Introduccion Java
Introduccion Java
Tipos de lexemas
Identificadores, palabras reservadas, literales, separadores y símbolos de operación.
Identificadores
Son palabras que permiten referenciar los diversos elementos que constituyen el código. Se
construyen mediante una secuencia de letras, dígitos, o los símbolos _ y $. En cualquier caso se
debe observar:
o No pueden coincidir con palabras reservadas de Java (ver más adelante)
2 Introducción a la Programación
Literales
Son elementos del lenguaje que permiten referenciar los distintos valores que pueden tomar
los tipos del lenguaje.
Ejemplo Tipo
2 int
3.1415926 double
‘a’ char
“rojo” String
1000L long
300.5f float
false boolean
null Objeto
o double
o float
o void – Tipo que no tiene ningún valor
Los tipos envoltura, como todos los objetos, tienen constructores para crear objetos. Estos
constructores tienen el mismo nombre que el tipo y diferentes posibilidades para los
parámetros: un valor del tipo primitivo correspondiente, una cadena de caracteres que
represente el valor, etc. Mediante un operador new seguido de uno de los constructores
podemos crear objetos de tipo envoltura.
Integer a = 35;
Integer b = new Integer(10);
Integer c = new Integer(“32”);
int d = 14;
int e;
e=a+b+c+d;
Además de las envolturas existen otros tipos que también son inmutables, como por ejemplo
las cadenas de caracteres o String.
Variables
Son elementos del lenguaje que permiten guardar y acceder a los datos que se manejan. Es
necesario declararlas antes de usarlas en cualquier parte del código y por convenio se escriben
en minúsculas. Mediante la declaración indicamos que la variable guardará un valor del tipo
declarado. Mediante una asignación podemos dar un nuevo valor a la variable.
Ejemplos:
int valor;
Double a1= 2.25, a2= 7.0;
char c= ´T´;
String cadena= “Curso Java”;
NOTA: String es un tipo de dato NO nativo de Java que se estudiará más adelante.
Constantes
Son elementos del lenguaje que permiten guardar y referenciar datos que van a permanecer
invariables durante la ejecución del código. La declaración de una constante comienza por la
palabra reservada final.
Es necesario declararlas y por convenio se hace con todas sus letras en mayúsculas.
Ejemplos:
Java proporciona los tipos array, List, String y Set para gestionar agregados lineales de
elementos del mismo tipo. Los objetos de los tipos array, List y String son agregados lineales
de elementos que pueden ser accedidos (indexados) a través de la posición que ocupan en la
colección. El tipo Set no es indexable. Cada uno de estos agregados lineales tiene un tamaño
que es el número de elementos que contiene. Los tipos indexables podemos considerarlos
formados por un conjunto de celdas. Cada celda está identificada de forma única por un índice.
Un índice es una variable de tipo int que representa la celda ubicada en una posición dada del
agregado indexable. Los valores del índice van de 0 al tamaño menos 1. La primera posición es
0 y la última, es el tamaño menos 1. Cada celda del agregado indexable tiene un índice y un
contenido. El contenido de la celda es un elemento del agregado. Un agregado indexable
podemos representarlo por:
…
[0] [1] [2] [3] … [tamaño-1]
Como cualquier variable cada uno de los agregados tiene un tipo y con ese tipo podemos
declarar variables. La declaración de un array de elementos de un tipo dado se hace
añadiendo [] al tipo. Los objetos de tipo String de la forma usual. Los objetos de tipo List<T> y
Set<T> los declararemos poniendo un tipo concreto dentro de <>.
int[] a;
Integer[] b;
String s;
List<Float> v;
Set<Integer> c;
Los elementos que puede contener un de agregado: array, List, String o Set tienen as
limitaciones siguientes:
Un array pueden contener elementos de tipos objeto y de tipos primitivos.
Los tipos Set o List pueden contener sólo elementos de tipo objeto.
Los elementos de un objeto de tipo String sólo pueden ser caracteres.
En resumen:
Hay por lo tanto diferentes tipos de operaciones que podemos hacer con cada una de los
agregados anteriores: declaración, consulta de la longitud, acceso al contenido de una celda y
modificación del contenido de una celda (en algunos casos). Como todas las variables las que
agregados de elementos tras ser declaradas deben ser inicializadas. Veamos como hacerlo en
cada caso.
2. Elementos del lenguaje Java 7
La inicialización para el caso de un array puede hacerse de dos maneras: indicando entre
llaves y separados por comas los valores iniciales del array o indicando solamente la longitud
del array en la forma new tipo[longitud] pero no el contenido de las celdas. En el caso del tipo
String la inicialización se hace dando un valor adecuado. En el caso de List<T> y Set<T> con el
operador new y el constructor adecuado. Más adelante veremos otros constructores posibles.
Ejemplos:
int[] a = {2,34,5};
Integer[] b = new Integer[7];
String s = “Hola”;
List<Float> v = new ArrayList();
Set<Integer> c = new HashSet();
Una vez que están inicializados podemos consultar su tamaño. Como hemos visto en el caso
de un array se hace con el atributo público length, en el caso de un String con el método
length() y en el caso de un List y Set con el método size(). La operación de consulta del tamaño
es una operación sin efectos laterales y devuelve un valor de tipo int.
En el ejemplo anterior r toma como valor la suma del número de elementos de a (3), s (4), v (0)
y c(0), es decir, 7. Obsérvese que el atributo length cuando es aplicado a un array no lleva
paréntesis porque es un atributo público. En el caso de un String es un método, igual que el
método size() para el caso de un Vector.
Como hemos visto arribas el acceso al contenido de una celda (en el caso de los agregados
indexables) permite obtener el contenido de una celda posición indicando el número de la
misma. Sólo está permitido acceder a las celdas que van de 0 a la longitud menos 1, si
intentamos acceder a otra celda se producirá un error en tiempo de ejecución que no es
8 Introducción a la Programación
La modificación del contenido de una celda no es posible para objetos de tipo String (ni para
agregados no indexables como Set) pero sí para objetos de tipo array y List. Cambiar el
contenido de la celda i en un array a por un nuevo valor e se consigue asignando e a a[i]. Es
decir a[i]=e. En caso de la lista v usando el método set(i,e).
a[1] = a[0];
En el ejemplo anterior a[0] es una operación de consulta porque está a la derecha del
operador de asignación. Devuelve el valor 2. La operación a[1] es una operación de
modificación de la celda 1 porque está a la izquierda del operador de asignación. A la celda 1
se le asigna el valor 2. El vector quedaría de la forma {2,2,5}.
En el caso de los agregado cuyo tamaño puede modificarse hay disponible operaciones para
añadir y eliminar elementos. Como se ha señalado esto no es posible en los objetos de tipo
array ni en los de tipo String. En los de tipo List y Sey sí es posible usando los métodos add(e),
para añadir el elemento y remove(e). En el caso de una lista el elemento añadido se colca al
final de la misma y se elimina, si hubiera varios iguales, el primero que encontremos
recorriendo la lista según índices crecientes de sus celdas.
elementos
array int [] a = a= {2,34,5}; a.length a[i] a[i] = e; No es posible
List<T> List<Float> v v= new v.size() v.get(i) v.set(i, e); v.add(i, e);
ArrayList(); v.remove(e);
Set<T> Set<Float> c c = new c.size() No es No es posible c.add(i, e);
HashSet(); posible c.remove(e);
String String s s = “Hola”; s.length() s.charAt(i) No es posible No es posible
Junto a la funcionalidad ya vista, el tipo String ofrece métodos para concatenar a la cadena
dada otra produciendo una nueva cadena (concat), decidir si la cadena contiene una secuencia
dada de caracteres (contains), buscar la primera posición de un carácter dado su valor entero
(indexOf), obtener la subcadena dada por dos posiciones incluyendo i1 y sin incluir i2
(substring), sustituir el carácter c1 en todas sus apariciones por el carácter c2, etc.
Junto a la funcionalidad vista los tipos Set<T> y List<T> ofrecen métodos para decidir si el
agregado está vacío (isEmpty()), para saber si contiene un objeto dado (contains(e)) .E l el tipo
List<T> específicamente algunos como (indexOf) que devuelve el índice de la primera celda
que contiene el elemento o add(i,e) que añade el elemento e en la casilla de índice i
desplazando a la derecha las de índice superior. O igual.
Los interfaces Set<T> y List<T> están en el paquete java.util (es necesario importarlo para su
uso). Incluimos las signaturas de algunos métodos del tipo List<T>. Los compartidos con
Set<T>, comentados anteriormente, tienen la misma signatura en Set<T> que en List<T>.
Los elementos que hay en una array, lista o conjunto pueden ser otro agregado de datos.
Veamos algunos ejemplos.
Define un array para 5 valores enteros cuyo valor está indefinido en general aunque para
algunos tipos se escogen los valores por defecto del tipo. En el caso de Integer el valor por
defecto es 0 por lo que en este caso se rellenarían con cero y para un valor de tipo objeto en
general null. Pero en general es siempre mejor suponer que el valor está indefinido.
● ? ? ? ? ?
v1
Define un array para 4 objetos que a su vez son arrays de enteros (array bidimensional)
0 0 0 0 0 0
0 0 0 0 0 0
0 0 0 0 0 0
0 0 0 0 0 0
O mediante conjuntos
Tanto en el caso de la lista como del conjunto construimos un agregado de tamaño 4 cada uno
de cuyos elementos es un agregado vacío.
Define un array para 4 objetos Punto con los valores devueltos por los constructores
respectivos
● ● ● ● ●
tablaPuntos
4. Expresiones y operadores
Conversiones de tipo
Para saber si una expresión está bien formada (no tiene errores sintácticos) tenemos que
comprender los mecanismos para calcular el tipo de una expresión. Estos mecanismos incluyen
las conversiones de tipos tanto automáticas o implícitas como explícitas.
Los tipos de datos primitivos tienen unas reglas sencillas de conversión. Entre ellos existe un
orden: int, long, float, double. Este orden nos permite deducir cuándo un tipo primitivo se
puede convertir en otro automáticamente.
Los tipos primitivos se convierten al nivel superior de forma automática cuando es
necesario.
En el sentido inverso las conversiones deben ser explícitas (mediante un operador de
casting). En este caso los decimales se truncan cuando pasamos de números reales a
enteros.
La conversión entre tipos de datos primitivos y sus correspondientes envolturas es
automática cuando es necesario.
Las operaciones aritméticas (+, -, *, /) se realizan siempre entre operandos del mismo tipo.
Para ello cuando se intenta hacer una operación aritmética entre dos tipos primitivos
distintos primero el tipo menor se convierte al mayor en el orden anterior. El tipo devuelto
es el mayor. La operación realizada es la correspondiente al tipo mayor. Así 5/2 es de tipo
entero y su resultado es 2. Sin embargo 5./2 es de tipo double y su resultado es 2.5. El
operador % (resto) toma dos operandos de tipo entero y devuelve un tipo entero.
Para comprender las reglas de conversión entre tipos objetos es conveniente partir del grafo
de tipos explicado en el tema anterior.
Un tipo se convierte automáticamente cuando es necesario en cualquier supertipo: un tipo
que es alcanzable des él en el grafo de tipos siguiendo flechas de herencia e
implementación.
El operador new seguido de un constructor devuelve objetos del tipo de la clase a la que
pertenece el constructor. Podemos concluir de las dos reglas anteriores que cuando se
crea un objeto su tipo puede ser convertido automáticamente en:
o El tipo de la clase a la que pertenece.
o El tipo de cualquiera de las interfaces que implementa su clase o alguna de sus
clases padre
o El tipo de cualquiera de sus supertipos del tipo asociado a la clase en el grafo de
tipos.
2. Elementos del lenguaje Java 13
int i = 7;
int j = 4;
int k = j;
boolean a = (i ==j ) ; // a es false
boolean b = (k ==j ) ; // b es true
El valor de a es false. Se comparan los valores de tipos primitivos y estos valores son distintos.
Después de asignar j a k sus valores son iguales luego b es true.
Los objetos p1 y p2 han sido creados con dos identidades distintas (cada vez que llamamos al
operador new se genera una nueva identidad) y no han sido asignados entre sí (no son
idénticos) pero son iguales porque sus propiedades los son. Por ello c es false y d es true.
El valor de a es true porque p1 ha sido asignado a p3 y por lo tanto tienen la misma identidad.
Por ello al modificar la propiedad X en p1 queda modificada en p3 y por lo tanto x1 vale 3.0.
Integer a = 3;
Integer b = a;
b++;
boolean e = (a==b); // e es false
Al ser el tipo Integer inmutable el valor de e es false. En la línea tres (b++) se crea un objeto
nuevo (con una nueva identidad) que se asigna a b. Si se elimina la línea 3 (b++;) entonces el
valor de e es true.
Operadores aritméticos
+ Suma
- Resta
* Producto
2. Elementos del lenguaje Java 15
/ División
% Módulo
Los operadores aritméticos toman operandos del mismo tipo y devuelven ese tipo. Si hubiera
dos tipos distintos primero se convierte del tipo menor al mayor. Los operandos deben ser de
tipos primitivos, si son de tipo envoltura entonces primero se produce una conversión
automática al tipo primitivo correspondiente. Los cuatro primeros operadores pueden tomar
operandos de cualquier tipo numérico. El operador % solo toma operandos enteros.
Operadores lógicos
&& y (and)
|| o (or)
! no (not)
Algunos operadores tienen una evaluación perezosa. Esto quiere decir que solo evalúan los
elementos relevantes para obtener el resultado. Los operadores lógicos && y || son de este
tipo. Así la expresión e1 && e2 evalúa primero e1 y si da como resultado false devuelve el
resultado false sin evaluar e2. Si e1 es true entonces evalúa e2 y devuelve el resultado. La
expresión e1 || e2 evalúa en primer lugar e1; si da true devuelve ese resultado sin evaluar e2.
Si la evaluación de e1 es false entonces evalúa e2 y devuelve el resultado. Esta forma de
evaluación hace que estos operadores no sean conmutativos y esto se puede aprovechar para
escribir algunas expresiones en una forma que no produzcan errores en tiempo de ejecución.
Por ejemplo:
Operadores de relación
> mayor que
< menor que
>= mayor o igual que
<= menor o igual que
== igual que/idéntico a
!= distinto de/no idéntico a
Si los operandos son tipos primitivos el operador == evalúa si el valor de los operandos es igual
o no. Si el tipo de los operandos es un tipo objeto en ese caso este operador evalúa si la
identidad de los operandos es la misma o no.
Otros operadores
. Invocar método
(tipo) Conversión de tipo
[] Acceso a posición de array
16 Introducción a la Programación
El operador ternario ?: tiene evaluación perezosa. Este operador toma tres operandos: e0, e1,
e2 en la forma e0?:e1:e2, donde e0 debe ser de tipo boolean. La semántica del mismo es la
siguiente: se evalúa e0, si es true evalúa e1 y devuelve el resultado (no evalúa e2) y si es false
evalúa e2 y devuelve el resultado (no evalúa e1).
Junto a los anteriores también está el operador de asignación (= , ya comentado arriba) y sus
formas asociadas +=, -=, etc.
Operadores de asignación
Abreviado No abreviado
a += b a=a+b
a -= b a=a–b
a *= b a=a*b
a /= b a=a/b
a %= b a=a%b
a++ a=a+1
a-- a=a-1
Si el tipo de los operandos en los operadores de asignación es un tipo objeto inmutable como
por ejemplo Integer entonces la equivalencia de a+=b es a = new Integer(a+b). Igual para los
otros operadores y tipos inmutables. Es decir se crea un objeto nuevo (una nueva identidad).
Igualmente para esos tipos inmutables la equivalencia de a++ es a = new Integer(a+1). Siempre
debemos tener en cuenta que los operandos de los operadores aritméticos deben ser tipos
primitivos.
Operador Asociatividad
. [] ()
+ - ! ++ -- (tipo) new derecha a izquierda
*/% izquierda a derecha
+- izquierda a derecha
< <= > >= izquierda a derecha
== != izquierda a derecha
&& izquierda a derecha
|| izquierda a derecha
?:
= += -= *= /= %= derecha a izquierda
2. Elementos del lenguaje Java 17
e1 e2;
e1 e2;
Una manera sencilla de definirlos es usando el operador ternario _?_ :_ . Las expresiones
equivalentes siguiente implementa el operador implica de forma perezosa:
!e1 ? true : e2
package java.lang;
public class Math{
//Existen versiones para double, float, int y long
public static double abs(double a);
public static int max(int a, int b);
public static long min(long a, long b);
public static double pow(double b, double e);
public static double random();
public static double sqrt(double a);
...
}
Otros cálculos con números enteros que pueden ser útiles los ubicamos en la clase Enteros.
Abajo aparecen métodos para decir si un entero es múltiplo o divisor de otro.
}
public boolean esPrimo(Integer a){
…
}
public Integer factorial(Integer a){
…
}
public Integer mcd(integer a, Integer b){
…
}
public Integer mcm(Integer a, Integer b){
…
}
…
Más adelante aprenderemos a diseñar los métodos que no están completos pero
supondremos que los tendremos disponibles para ser usados en otros ejercicios. Son métodos
que calculan la factorial, si un número es primo, el máximo común divisor, el mínimo común
múltiplo, etc.
5. Sentencias de control
El cuerpo de los métodos de una clase está formado por una serie de unidades elementales
que llamaremos sentencias. La sentencia más sencilla es una expresión. Ahora introduciremos
unas sentencias más complejas como son las sentencias de control. Estas sentencias sirven
para romper el orden lineal de ejecución que hemos visto hasta ahora en las secuencias de
expresiones.
if (condición) {
sentencia-1;
...
sentencia-n;
} else {
sentencia-n+1;
...
2. Elementos del lenguaje Java 19
sentencia-m;
}
sentencia-1 sentencia-n+1
sentencia-2 sentencia-n+2
... ...
sentencia-n sentencia-m
Sintaxis:
while (condición) {
sentencia-1;
sentencia-2;
...
sentencia-n;
}
20 Introducción a la Programación
condición Falsa
Cierta
sentencia-1
sentencia-2
...
sentencia-n
inicialización
condición Falsa
actualización
Cierta
sentencia-1
sentencia-2
...
sentencia-n
¿Quedan NO
elementos por
recorrer?
SÍ
sentencia-1
sentencia-2
...
sentencia-n
int [] t = {1,3,5,7,11};
s = 0;
for (int i= 0; i < t.length; i++) {
s = s + t[i];
}
Código que calcula en la variable s la suma de los números contenidos en el array t mediante
un for-each
int [] t = {1,3,5,7,11};
s = 0;
for (int e : t) {
s = s + e;
}
for/while ( ){
sentencia-1;
…
if (…) continue;
...
sentencia-n;
}
for/while ( ){
sentencia-1;
…
if (…) break;
...
sentencia-n;
}
hacerse con sentencias if-else pero cuando se trata de comparar una variable que toma valores
discretos con cada uno de ellos y el número de los mismos es 3, 4 ó superior entonces es
preferible, por legibilidad del programa y eficiencia, una sentencia switch. Su sintaxis es:
switch( variable ){
case valor1: sentencias; break;
case valor2: sentencias; break;
...
case valorN: sentencias; break;
default: sentencias;
}
Se ejecutan las sentencias del case cuyo valor es igual al de la variable. Si el valor de la variable
no coincide con ningún valor, entonces se ejecutan las sentencias definidas en default, si es
que las hay.
6. Gestión de excepciones
Las excepciones son, junto con las sentencias de control vistas anteriormente, otro mecanismo
de control. Es decir, es un instrumento para romper y gestionar el orden en que se evalúan las
sentencias de un programa.
Se denomina excepción a un evento que ocurre durante la ejecución de un programa, y que
indica una situación normal o anormal que hay que gestionar. Por ejemplo, una división por
cero o el acceso a un fichero no disponible en el disco. Estos eventos pueden ser de dos
grandes tipos: eventos generados por el propio sistema y eventos generados por el
programador. En ambos casos hay que gestionar el evento. Es decir, decidir qué sentencias se
ejecutan cuando el evento ocurre.
Cuando se produce un evento como los comentados arriba decimos que se ha disparado una
excepción. Cuando tomamos decisiones después de haberse producido un evento decimos
que gestionamos la excepción.
Hasta ahora hemos visto los métodos de los diferentes tipos como mecanismos adecuados
para llevar a cabo una acción pasándole unos parámetros y posiblemente obteniendo un
resultado. Ahora un método puede generar excepciones que habrá que gestionar si alguna de
ellas se dispara durante su llamada. Hay una segunda clasificación de las excepciones que se
pueden generar dentro de un método: las que estamos obligados a declarar en la signatura del
mismo y las que no. Vemos, por lo tanto, que la signatura de un método incluirá además del
nombre, los parámetros formales y el tipo devuelto las excepciones que pueden generarse
dentro del método y que estamos obligados a declarar.
Cuando se dispara una excepción tras la ocurrencia de un evento se crean objetos que
transportan la información del evento. A estos objetos también los llamamos excepciones. En
cada programa necesitamos excepciones (objetos) de tipos específicos. Esto lo conseguimos
diseñando clases para este fin. Algunas vienen ya predefinidas y otras tenemos que diseñarlas.
A este proceso lo llamamos diseño de excepciones. Veamos con más detalle cada uno de los
aspectos de la programación con excepciones.
2. Elementos del lenguaje Java 25
Diseño de Excepciones
Es el mecanismo de diseño de las nuevas clases que nos permitirán crear los objetos que se
crearán cuando se dispara una excepción. El entorno Java ya tiene un conjunto de excepciones
predefinidas y una jerarquía de las mismas. La figura 5 muestra la jerarquía de Excepciones,
incluyendo algunas de las más notables de la API de Java.
Las excepciones heredan del tipo Throwable, aunque en este texto sólo prestaremos atención
a las que heredan del tipo Exception. Para nosotros, por lo tanto, los objetos que se crean al
dispararse una excepción serán de un tipo hijo de Exception.
Hay un subtipo específico de Exception que se denomina RuntimeException. Si una excepción
hereda de RuntimeException no es obligatorio declararla en la signatura aunque pueda ser
generada en el cuerpo del método. Una excepción que se puede generar en el cuerpo de un
método pero que no extienda RuntimeException, aunque sí a Exception, tiene que ser
declarada en la signatura obligatoriamente. Esta característica es muy importante a la hora de
diseñar las excepciones: al diseñar una nueva excepción y por tanto una nueva clase, debemos
decidir si hereda de RuntimeException o no, esto es, directamente de Exception. Esta decisión
influirá sobre las signaturas de los métodos y el código de las llamadas a los mismos como
veremos más adelante.
Object
Throwable
Exception Error
Nuestras
IOException CloneNotSuportedException RuntimeException ...
excepciones
IndexOutOfBounds Arithmetic
Exception Exception
StringIndexOutOfBounds ArrayIndexOutOfBounds
Exception Exception
Ejemplo
y en las clases:
tipo nombre-método (parámetros-formales) throws ClaseException1, ClaseException, … {
cuerpo
}
Ejemplo:
public FileReader(String fileName) throws FileNotFoundException;
Como vemos es una sentencia if que evalúa una condición y si es cierta dispara una excepción.
Llamaremos Condición de Disparo a esa condición que si es cierta disparará la excepción. Cada
excepción tiene asociado un evento de disparo. El evento ocurre cuando la Condición de
Disparo se hace verdadera. La Condición de Disparo es una expresión lógica que depende de
los parámetros del método y de las propiedades del objeto. La cláusula throw
MiExcepcion(“TextoExplicativo”) crea un objeto del tipo adecuado y dispara la excepción. Por
cada posible excepción a disparar debemos escoger una Condición de Disparo (en general un
evento) que cuando se hace verdadera se dispare la excepción.
Vemos que hay dos cláusulas: throw y throws. La primera sirve para disparar excepciones. La
segunda para declararlas en las signaturas de los métodos.
2. Elementos del lenguaje Java 27
Las excepciones disparadas por sistema (por ejemplo las que pueden aparecer en operaciones
aritméticas o las que se disparan cuando se intenta acceder a una celda no permitida de un
Vector o un array y que provienen del hardware), tienen un funcionamiento parecido.
Cuando un método m1 llama a otro m2 entonces m1 puede gestionar las excepciones que m2
pueda generar o alternativamente propagarlas al método que llamó a m1. Es decir no
gestionarlas. Si m2 declara en su signatura la posibilidad de generar excepciones mediante la
cláusula throws entonces m1 tiene la obligación, en su código, de gestionar todas las
excepciones declaradas.
Las excepciones no gestionadas se propagan desde cada método al que lo ha llamado hasta
que se encuentra un método que las gestione. Si ningún método gestiona una excepción, ni
tampoco el programa principal, entonces el programa termina de forma abrupta.
try {
Sentencias0;
} catch (TipoDeException1 e1) {
Sentencias1;
} catch (TipoDeException2 e2) {
Sentencias2;
} finally {
Sentenciasf;
}
Las sentencias contenidas en el bloque del try, Sentencias0, deben contener la llamada al
método m2. El bloque try es el código donde se prevé que se genere una excepción. Es como si
dijésemos "intenta estas sentencias y mira a ver si se produce una excepción".
Resumiendo podemos decir que la llamada al método que puede disparar excepciones se hace
dentro del bloque del try. Si el método llamado funciona en modo normal termina y
posteriormente se ejecutan las sentencias del bloque finally. Si el método funciona de modo
excepcional y hay un bloque catch para gestionar la excepción disparada entonces se ejecutan
las sentencias del bloque catch y después las del bloque finally. Si el método funciona en modo
excepcional y no hay ningún bloque catch para gestionar la excepción disparada se ejecutan
las sentencias del bloque finally y se propaga la excepción al método que llamó a m1.
El bloque finally es opcional. También son opcionales las cláusulas catch. Pero siempre debe
haber una cláusula finally o al menos una cláusula catch detrás del bloque try.
En el ejemplo anterior no se gestiona la excepción que aparece al intentar la división por cero.
El programa termina con el mensaje:
En el bloque try se ha producido una excepción al intentar dividir por cero. Como no se ha
podido hacer la división no se ha asignado ningún valor a n cuyo valor sigue siendo null. La
2. Elementos del lenguaje Java 29
excepción disparada es ArithmeticException. Como existe un bloque catch para gestionar esta
excepción se ejecuta el código correspondiente, después se ejecutará el código posterior a las
cláusulas try/catch.
El mecanismo de paso de parámetros varía de unos lenguajes a otros. Recordamos del tema 1
que los parámetros formales son variables que aparecen en la signatura del método en el
momento de su declaración. Los parámetros reales son expresiones que se colocan en el lugar
de los parámetros formales en el momento de la llamada al método.
Supongamos el siguiente método m (en este caso static) que es llamado desde el método
main:
s += “ Modificado”;
d = i;
p.setX(d);
return d;
}
i1 = 14
s1 = “Valor Inicial”;
d1 = 0.75
p1 = (15.0,4.5)
En el ejemplo anterior los parámetros formales son i, s, d, p cuyos tipos son Integer, String,
double, Punto. Es decir y por este orden, dos objetos inmutables, un tipo primitivo y un objeto
mutable. Como podemos observar por el resultado las operaciones en el cuerpo del método m
han modificado el parámetro real p1 (tipo objeto mutable) pero no los parámetros reales i1,
s1, d1 (tipos inmutables, tipo primitivo). Es decir los parámetros i, s, d se han comportado
como parámetros de entrada y p como parámetro de entrada-salida. Esto ocurre en general:
los tipos inmutables y los tipos primitivos se comportan como parámetros de entrada y los tipos
objeto mutables como parámetros de entrada-salida. Pero ¿por qué ocurre así? Para
entenderlo vamos a transformar el programa en otro equivalente que nos permita
comprender las propiedades del paso de parámetros a partir de las propiedades de la
asignación.
Veamos en primer lugar los tipos primitivos. En estos la asignación cambia los valores del
operando de la derecha al de la izquierda. Pero estos tipos no tienen identidad. Por lo tanto el
parámetro formal d recibe el valor del correspondiente parámetro real d1 pero las
modificaciones en el valor del parámetro formal d no afectan al parámetro real d1. Es un
parámetro de entrada.
En los tipos objetos inmutables (Integer, String, …) la asignación asigna la identidad de los
operados de la derecha a los operandos de la izquierda. En las primeras líneas i, s tienen la
mima identidad que i1, s1. Pero las operaciones sobre estos tipos (i++, s+= “ Modificado”)
generan una nueva identidad que queda guardada en i, s. Por lo tanto los cambios en los
parámetros formales no afectan a los parámetros reales. Como en el caso de los tipos
primitivos son parámetros de entrada.
En el caso de los parámetros de tipo objeto mutable los parámetros formales y los reales
comparten la misma identidad, tras la asignación. Los cambios en los parámetros formales
afectan a los parámetros reales. Por tanto, son siempre parámetros de entrada-salida.
El operador varargs, que se denota por “…”, puesto detrás del tipo de un parámetro formal
indica que el método aceptará una secuencia variable de parámetros del mismo tipo.
El tratamiento de los parámetros de este tipo se hará como si el parámetro formal fuera una
array del tipo correspondiente. Es decir si T es un tipo, la expresión T… es equivalente en su
tratamiento a T[]. Sin embargo la forma de aportar los parámetros reales es distinta. Si el
parámetro formal es T[] el parámetro real debe ser un array de T. Pero si el parámetro formal
es T… los parámetros reales son una secuencia de expresiones de tipo T separadas por comas.
El resultado es
Hola Pablo.
Hola Antonio.
Hola Juan.
El operador varargs (…) siempre se coloca al final en la signatura del método y se debe evitar
su uso con métodos sobrecargados.
En Java existe la posibilidad de usar tipos genéricos. Estos son tipos que dependen de
parámetros formales que serán instanciados posteriormente por tipos concretos. En Java las
interfaces, las clases y los métodos pueden ser genéricos. Esto quiere decir que tienen
parámetros en su definición que posteriormente se instanciarán por tipos concretos. En este
caso decimos que son interfaces, clases o métodos genéricos. Se denomina Programación
Genérica a la programación que usa tipos, interfaces, clases y métodos genéricos.
La programación genérica permite programas más reutilizables y con mayor tolerancia a
fallos.
T[] a;
Con la declaración anterior decimos que a es un array cuyos elementos son de tipo T.
Como primer ejemplo de interfaz genérica tenemos la interfaz Comparable que estudiaremos
con más detalle en el tema siguiente:
interface Comparable<T> {
int compareTo(T e);
}
Esta interfaz solo tiene un método, compareTo, cuyo objetivo es comparar el objeto this con el
objeto e y devolver 0, un número negativo o positivo según que this sea igual, menor o mayor
que e, respectivamente. Vemos que la interfaz genérica Comparable tiene un parámetro
formal T. Esto se declara escribiendo <T> después del nombre de la interfaz. Ese parámetro,
34 Introducción a la Programación
una vez declarado, puede ser usado para construir tipos genéricos, declarar tipos de
parámetros formales o tipos devueltos por métodos. Puede haber más de un parámetro
formal. En este caso la declaración de los parámetros genéricos se hace incluyéndolos
separados por comas entre <>. La declaración de los parámetros genéricos siempre va detrás
del nombre de la interfaz.
Una interfaz genérica define un nuevo tipo genérico que puede ser usado en declaraciones
posteriores. En este caso el tipo genérico definido es Comparable<T>.
El segundo ejemplo es el tipo List<T> que vimos al principio del tema. Este tipo es genérico:
int size();
T get(int index);
T set(int index, T element );
boolean add(T element );
void add(int index, T element );
boolean isEmpty();
boolean contains(Object o);
int indexOf(Object o);
…
}
En el ejemplo anterior aparece un método genérico de una clase no genérica (Utiles). Este
método devuelve una lista con elementos de tipo T que contiene n copias idénticas del objeto
que recibe como parámetro.
NOTA: este apartado contiene información más detallada y se deja como lectura recomendada
al alumno que desee ampliar sus conocimientos en este tema.
Sobre los tipos genéricos podemos hacer una operación que llamaremos operación de
borrado (en inglés erasure). Esta operación toma un tipo genérico y devuelve el tipo desnudo
(raw type, es más descriptivo tipo desnudo que la acepción más literal tipo crudo)
correspondiente. Un tipo desnudo es no genérico. La operación de borrado de un tipo
genérico elimina todo lo que va entre <> y devuelve un tipo desnudo, es decir sin <_> detrás
del nombre del tipo. Por ejemplo la operación borrado de List<T> devuelve ListListes un tipo
desnudo correspondiente a List<T>.
Debemos tener en cuenta que los tipos genéricos, en Java, sólo existen en tiempo de
compilación. En tiempo de ejecución sólo existen los tipos desnudos correspondientes al tipo
genérico. Esto implica que determinados operadores que tienen un tipo como operando sólo
admitirán tipos no genéricos. Es el caso del operador instanceof. Este operador sólo admitirá
como segundo operando un tipo concreto o el tipo desnudo de un tipo genérico. Este
comportamiento de los tipos genéricos en Java será muy importante más adelante.
El hecho de que los tipos genéricos no existan en Java en tiempo de ejecución, restringe
también la posibilidad de constructores de un tipo T que ha sido declarado como un parámetro
genérico, al igual que ocurre con los inicializadores de array. Si un tipo de la forma T[] (array de
T) no puedan ser instanciado en tiempo de compilación no se permiten constructorees e
incializadores del mismo. Esto es debido a que el compilador no tiene los detalles para
construir los objetos del tipo genérico. Dan errores de compilación sentencias del tipo dos,
tres, cuatro y cinco siguientes:
9. Conceptos Aprendidos
Operador varargs
Tipos genéricos y programación genérica
10. Ejercicios
4. Declare variables para almacenar cada uno de los valores anteriores e inicialícelas con los
mismos. ¿Qué añadiría a la declaración para que se convirtiesen en constantes?
5. ¿Cuáles de las siguientes expresiones son correctas en Java? Considere declaradas las
siguientes variables:
(i+j) < d
(i+j) < c
(i+j) != 5.0f
(b == i) + (j != 5.0f)
c!=s
s+=(s+s)
38 Introducción a la Programación
(b =! (c>c2)) || ((f-d)==(i-j))
f++;
(j%=10) == 0
c2=c
6. ¿Cuáles de las siguientes expresiones son correctas en Java? Considere declaradas las
siguientes variables:
i+j < d
i+j < c
i+j != 5.0f
b == i + j != 5.0f
s+=s+s)
b =! (c>c2) || (f-d) == (i-j)
j%=10 == 0
7. Indique el tipo de cada una de las expresiones de los dos ejercicios anteriores.
8. Señale el valor que devuelve cada una de las expresiones de los ejercicios anteriores.
9. Declara una variable llamada fila de tipo array de Integer. Crea el objeto que puede
almacenar 100 enteros.
10. Declara una variable llamada columna de tipo array de Double. Crea el objeto que puede
almacenar 100 reales.
11. Declara una variable llamada matriz de tipo array de array de Character. Crea el objeto de
este tipo que pueda almacenar 8x10 caracteres.
12. Declara un array llamado primos e inicialízalo para que contenga los diez primeros
números primos (1, 2, 3, 5, 7, 11, 13, 17, 19, 23).
13. Declara una matriz llamada pares e inicialízala con 3 filas de 4 columnas cada una, todas
ellas con números pares.
14. Declara e inicializa dos variables de tipo cadena de texto. Declara e inicializa una variable
lógica que indique si las dos cadenas anteriores son iguales.
15. Crea una nueva cadena de texto como resultado de pasar a mayúsculas alguna de las dos
anteriores.
16. Crea una nueva cadena de texto como resultado de eliminar los espacios en blanco de la
anterior.
17. Declara e inicializa una variable lógica que indique si dos de las cadenas anteriores tienen
el mismo tamaño.
18. Crea una nueva cadena de texto como concatenación de todas las anteriores.
19. Declara e inicializa una variable lógica que indique si alguna de las cadenas anteriores
contiene el carácter ‘C’.
20. Crea una nueva cadena de texto como resultado de sustituir el carácter ‘s’ por el carácter
‘$’ en alguna de las cadenas anteriores.
21. Crea una lista de enteros llamada v y añade en los 10 primeros números impares.
22. Declara e inicializa una variable lógica que indique si la lista anterior contiene el elemento
0.
23. Declara e inicializa una variable entera con el valor del 6º elemento de la lista anterior.
24. Declara e inicializa una variable entera con el índice de la primera aparición del valor 3 en
la lista anterior.
2. Elementos del lenguaje Java 39
33. Indique el valor de la variable “cadena” justo antes de acabar de ejecutarse el código del
método main.
34. En el siguiente bloque de código identifique, si las hay, las variables inútiles y el código
inútil. Supóngase que la variable a, de tipo int, está declarada en un ámbito superior.
{
int x = 10; int y; int c;
x= x+2;
y = 14;
x = y+ 5;
a = x;
x = c + 2;
}
40 Introducción a la Programación
Suponiendo que no tenemos en cuenta las inicializaciones por defecto hay alguna variable
que se esté usando antes de haber sido inicializada?.
35. Implementa los métodos de la clase Enteros vista en el capítulo sabiendo que:
Un número entero es primo si no es divisible por ningún número entero comprendido
entre 2 y su raíz cuadrada. Compruebe en el problema que el parámetro
proporcionado es un número entero positivo. Dispare la excepción
IllegalArgumentException si no se cumple esa condición
El máximo común divisor de dos números enteros cumple las siguientes propiedades:
mcd(a,0) == a, mcd(a,b) == mcd(b,a%b).
El mínimo común múltiplo de dos enteros cumple la propiedad:
mcd(a,b)*mcm(a,b)==a*b siempre que a y b sean distintos de cero.
Implemente un método static en la clase Enteros que imprima en pantalla los enteros
que van desde a hasta b con incremento de c.
Implemente un método static en la clase Reales que imprima en pantalla los números
reales que van desde a hasta b con incremento de c.
36. Escriba la clase TestEjercicio1 que hereda de Test con un método main que
a) Cree dos listas vacías de Character, añada a la primera ‘S’, ‘E’, ‘M’, ‘A’, ‘N’, ‘A’ y a la
segunda ‘R’, ‘A’, ‘M’, ‘O’ y las muestre por pantalla.
b) Muestre el cardinal de cada una de las listas.
c) Pregunte si una de las listas contiene una letra dada.
d) Pregunte en qué posición está una letra que sí está en una lista y otra que no está.
e) Añada al la primera lista ‘S’, compruebe el valor devuelto por la operación y muestre la
lista resultante.