declaraciones y control de acceso

113
1 Declaraciones y Control de acceso (I) Identificadores en Java Un identificador es un nombre asignado a un paquete, clase, interface, método o variable. Un identificador en Java debe comenzar por una letra, el signo de subrayado “_” o símbolo de moneda. Los siguientes caracteres pueden ser cualquier combinación de letras, números, caracteres de moneda o conectores. No tienen longitud máxima. Se diferencia entre mayúsculas y minúsculas. Un identificador no puede ser una palabra clave de Java, pero puede contener una palabra clave como parte de su nombre. Ejemplo de literales válidos:

Upload: siquem-abner-bello-tapia

Post on 29-Nov-2015

26 views

Category:

Documents


4 download

TRANSCRIPT

1

Declaraciones y Control de acceso (I)

Identificadores en Java

Un identificador es un nombre asignado a un paquete, clase,

interface, método o variable.

Un identificador en Java debe comenzar por una letra, el signo de

subrayado “_” o símbolo de moneda.

Los siguientes caracteres pueden ser cualquier combinación de

letras, números, caracteres de moneda o conectores.

No tienen longitud máxima.

Se diferencia entre mayúsculas y minúsculas.

Un identificador no puede ser una palabra clave de Java, pero

puede contener una palabra clave como parte de su nombre.

Ejemplo de literales válidos:

2

Ejemplo de literales no válidos:

Palabras clave en Java

Son palabras reservadas aquellas que tienen un significado

especial para el compilador de Java.

Se suelen mostrar en un cuadro alfabéticamente pero creo que es

más fácil recordarlas agrupandolas por categorías.

Tipos de datos (10) : byte, short, int, long, float, double,

boolean, char, enum, void

Flujo de datos (11) : if, else, for, do, while, switch, case,

break, default, continue, return

Excepciones(6) : try, catch, finally, throw, throws, assert

Operador(1) : instanceof

NoUso(2) : const, goto

Objetos(9) : package, import, class, interface,extends,

implements, new, this, super

Modificadores(11) : abstract, final, static, private, protected,

public, strictfp, synchronized, transient, volatile, native

true, false y null son literales, se escriben en minúsculas, y

tampoco pueden ser usados como identificadores.

3

Convenciones de codificación Java

Paquetes: en minúsculas (transporte.objetos, utiles.factura)

Clases e Interfaces: la primera letra en Mayúscula

(FacturaElectronica, Facturable)

Métodos: combinaciones verbo-nombre en camelCase. (getCliente,

setAltura, sumarAsientos)

Variables: nombres en camelCase. (nuevoCliente,

saldoMedioInteranual)

Constantes: en Mayúsculas y si hay que separar usar el subrayado

“_” (IPC, TOTAL_NOMINA)

Estructuras de control: cuando las sentencias forman parte de una

estructura de control de flujo,

escribirlas entre llaves, aunque sean sentencias sencillas.

Espacios: solo debe colocarse una sentencia por línea y utilizar

sangrías de dos o cuatro espacios para facilitar la lectura.

Comentarios: utilizar comentarios para explicar segmentos de

código no obvios.

JavaBeans: son clases de Java que tienen sus atributos privados.

Los métodos para obtener estos atributos se denominan getters y

los métodos para establecer estos atributossetters.

(getLongitud(), getFechaNacimiento(), isEncendido(),

setLongitud(l), setFechaNacimiento(fecha), setEncendido(true))

Declaraciones y control de acceso (II)

Declaración de una clase

Una clase es una plantilla que describe las propiedades y el

comportamiento que van a tener los objetos de esa clase.

La sintaxis para declarar una clase es la siguiente:

[Modificadores] class NombreClase [extends SuperClase]

[implementes Interface]{}

La mínima expresión de una declaración de clase sería:

class MinimaClase{}

Los modificadores y las cláusulas de herencia e interfaces son

opcionales.

4

Reglas de Declaración en el fichero fuente

Solo puede existir una clase pública en un fichero .java

El nombre del fichero debe coincidir con el de la clase

pública.

La sentencia package (si existe) debe ser la primera

sentencia del fichero.

Las sentencias import (si existen) deben seguir a la

sentencia package y preceder a las declaraciones de clases.

Pueden existir más clases en el fichero pero no pueden ser

públicas.

Las sentencias package e import afectarán a todas las clases

declaradas en el fichero.

El nombre de los ficheros que solo tengan declaraciones de

clase no públicas no tiene que coincidir con ninguna de las

clases.

Modificadores de acceso

Existen tres modificadores de acceso: public, protected y private.

Sin embargo, existen cuatro niveles de acceso. Cuando no se

especifica ninguno de los tres modificadores anteriores se tiene

el nivel de acceso por defecto, que es el nivel de paquete.

Para las clases de primer nivel solo se permite:

public

nivel de paquete

Para atributos, métodos, clases anidadas, se permiten todos.Los

explicamos un poco más, desde el más restrictivo al menos

restrictivo:

private: solo es accesible dentro de su clase.

no se especifica (nivel de paquete): es

accesible dentro de su clase y por

todas las clases de su paquete.

protected: es accesible dentro de

su clase, por todas las clases de

su paquete y por las clases hijas

que estén en otro paquete

diferente.

public: es accesible para

cualquier clase Java.

5

Otros Modificadores

Otros modificadores que pueden añadirse despúes de los

modificadores de acceso son:

strictfp

abstract

final

Strictfp

puede modificar una clase o un método, nunca una variable.

Cuando marcamos una clase como strictp significa que todo el

código de la clase sigue la especificación IEEE754 para flotantes.

Cuando marcamos strictfp a un método el código del método sigue la

especificación anterior.

Si cumplimos esta especificación prevenimos que los números

flotantes puedan ser dependientes de la plataforma.

Final

Cuando marcamos una clase como final estamos indicando que esta

clase no puede ser extendida en subclases.

Los métodos marcados como final no pueden sobrescribirse.

Si una variable se marca como final, se convierte en una

constante.

Abstract

Cuando marcamos una clase como abstract estamos indicando que no

se puede instanciar. Su objetivo es ser extendida en subclases.

Una clase abstracta puede tener tanto métodos abstractos como no

abstractos. Un solo método abstracto obliga a declarar la clase

como abstracta.

Este tipo de clases son útiles cuando la implementación queremos

que se concrete en sus clases hijas.

No tiene sentido declarar una clase final y abstracta.

6

Declaraciones y control de acceso (III)

Interfaz

La interfaz pública de una clase es un “contrato” entre el código

cliente y la clase que proporciona el servicio.

Decimos que una clase implementa una interfaz cuando implementa

todos los métodos declarados en ella.

Varias clases pueden implementar la misma interfaz. Una sola clase

puede implementar varias interfaces.

La sintaxis para declarar una interfaz es la siguiente:

[Modificadores] interface NombreInterface [extends

InterfacePadre]{

<public static final atributos>

<public abstract metodos>

}

Todos los métodos declarados en una interfaz son public y

abstract, aunque no se especifique.

Todos los atributos en una interfaz son public, static y

final, aunque no se especifique. Es decir, constantes.

Los métodos no pueden ser static ni final, strictfp, o native.

Una interfaz puede extender solo otras interfaces.

Las interfaces se usan para:

declarar métodos que serán implementados por una o varias

clases.

dar a conocer la interfaz de programación sin revelar la

implementación

identificar similitudes entre clases no relacionadas

simular la herencia múltiple declarando una clase que

implemente varias interfaces

Variables Locales y Modificadores

Los modificadores de acceso no son aplicables a variables locales,

provocaría error de compilación.

El único modificador que podría aplicarse a variables locales

sería final.

Modificadores de acceso para miembros de una clase

Se pueden aplicar: public, protected, paquete (por defecto),

private.

7

Otros modificadores para miembros de una clase

Strictfp

Para miembros de clase sólo se puede aplicar a métodos. Cuando

marcamos strictfp a un método el código del método sigue la

especificación anterior IEEE754 para flotantes.

Final

Los métodos marcados como final no pueden sobrescribirse.

Los argumentos marcados como final no pueden cambiar su valor.

Si una variable se marca como final, se convierte en una

constante.

Abstract

Un método abstracto se declara pero no se implementa. Acaba en ;.

Un solo método abstracto obliga a declarar la clase como

abstracta.

La primera subclase concreta debe implementar todos los métodos

abstractos.

Static

Pueden marcarse static: métodos, variables, clases anidadas y

bloques de inicialización.

Crea miembros que están asociados a la Clase y no necesitan que

exista un objeto instanciado de la Clase para existir.

Synchronized

Sólo métodos pueden marcarse synchronized. Significa que solo un

hilo puede acceder al método a la vez.

Native

Sólo métodos pueden marcarse native. Indica que el método está

implementado en un lenguaje que es dependiente de la plataforma,

usualmente C. Su declaración acabará en ‘;’ como los métodos

abstractos, ya que su implementación no se especifica.

Transient variable

Si marcamos una variable de instancia transient no se añadirá en

la serialización del objeto que la contenga.

Volatile variable

Indica al hilo que accede a la variable que siempre debe

reconciliar su copia privada con la copia en memoria. Solo aplica

a variables de instancia.

8

Argumentos variables

Para especificar un método con un número variable de argumentos,

se define el tipo seguido de ‘…’, un espacio y un nombre para el

array asociado.

El argumento variable debe ser el último en la declaración del

método y solo se permite uno.

[Modificador] ValorDeRetorno NombreMetodo (Tipo… a) {}

Constructores

Un constructor es un conjunto de sentencias para inicializar una

instancia.No se consideran métodos. No tienen valores de retorno

ni se heredan.

El nombre del constructor debe coincidir con el de la clase.Cada

clase tiene al menos un constructor. Si no se escribe ninguno Java

suministra uno por defecto. En ese caso el constructor no tiene

argumentos y su cuerpo está vacío.

En el momento que nosotros escribamos un constructor se pierde el

constructor por defecto.

La sintaxis es:

[Modificadores] nombreClase (<argumentos>){}

Declaraciones y control de acceso (IV)

Variables

Por el tipo se pueden dividir en:

variables de tipos primitivos.

variables de tipos de referencia.

Por el lugar en que se definen:

variables locales (dentro de un método, parámetros del

método)

variables miembro (fuera de un método, en la definición de la

clase)

variable de clase (fuera de un método, en la definición de

una clase, lleva la palabra static)

Tipos Primitivos en Java

Los tipos primitivos son simples valores, no objetos. Los tipos de

referencia se utilizan para tipos más complejos, incluidos los que

declara el propio programador.

9

Java define ocho tipos de datos primitivos, que se dividen en

cuatro categorías:

Lógicos: boolean

Texto: char

Enteros: byte, short, int, long

Reales: float, doublé

Boolean

Los valores lógicos se representan mediante el tipo boolean, que

puede contener uno de estos dos valores: true (verdadero) o false

(falso).

Decimos que el tipo boolean admite dos valores literales: true y

false.

Char

Los caracteres se representan mediante el tipo char. Un valor char

representa un carácter Unicode de 16 bits sin signo. Los literales

char se escriben entre ‘ ‘.

Enteros

Hay cuatro tipos de datos enteros en Java. Cada uno de ellos se

declara usando las palabras clave byte, short, int o long.

Se puede representar los literales de los tipos enteros utilizando

una notación decimal, octal o hexadecimal.

Por ejemplo:

70 sería 70 en decimal, 046 en octal y 0x106 en hexadecimal.

Todos los tipos numéricos en Java representan números con signo.

Por defecto los literales enteros son del tipo int, a menos que

vayan seguidos de la letra L, que indicaría que es un long.

Por ejemplo:

24L indicaría que el valor decimal 24 se representaría como un

entero largo.

La tabla siguiente indica el tamaño en bits de cada tipo y su

rango de valores:

10

Reales

Las variables de tipo real se declaran con las palabras float o

double. Un literal numérico es un número real si incluye un

separador decimal y un exponente, o bien si va seguido de la letra

F o f (float) o de la letra D o d (double).

Los literales reales se consideran double de forma predeterminada.

Es posible declarar literales del tipo float agregando F o f al

valor.

Ejemplos:

5.15 Valor corto de precisión simple, pero por

defecto sería double.

3.35E25 Valor largo double

4.393F Valor corto de precisión simple (float)

76.5E800D Valor largo double

Tabla de los reales:

Declarando variables primitivas

Pueden ser declaradas como variables de clase (static), como

variables miembro o como variables locales.

Se pueden declarar en una misma linea varias variables del mismo

tipo.

Ejemplos:

1

2

3

4

5

6

7

8

9

boolean resultado, existe, estaEncendido; //declaraciones

de variables locales

char nuevaLinea;

byte b;

public class claseX {

public static int contador = 0; //ejemplo de declaración

de variable de clase

private int codigo; //ejemplo de declaración

de variables miembro.

private boolean hayExistencias;

}

11

Declarando variables de referencia

Pueden ser declaradas como variables de clase (static), como

variables miembro o como variables locales.

Se pueden declarar en una misma linea varias variables del mismo

tipo.

Ejemplos:

1

2

3

4

5

6

7

8

9

10

11

String nombre, direccion, provincia; // declaración de

variables de referencia locales

MiObjeto x;

Informe i;

public class Cliente{

public static Cuenta[] cuentasVIP = new Cuenta [100];

//declaración como variable de clase

private String nombre; //declaración de variables de

referencia como miembros

private Domicilio domicilioActual;

private Cuenta cuenta;

...

}

Variables Locales

Las variables que se definen dentro de un método se denominan

locales, aunque algunas veces también reciben el nombre de

variables temporales o de pila.

Es preciso inicializarlas antes de usarlas por primera vez, de lo

contrario el compilador dará error.

Los parámetros de los métodos y los constructores también son

variables locales, pero las inicializa el código de llamada.

Las variables locales se crean cuando el programa empieza a

ejecutar el método y se destruyen cuando finaliza dicha ejecución.

Las variables que se definen dentro de los métodos de una clase

son locales para ese método, por lo que es posible usar el mismo

nombre de variable en distintas funciones miembro para hacer

referencia a distintas variables.

12

El único modificador que pueden usar las variables locales es

static.

Variables Miembro y de Clase

Las variables de clase, marcadas con static, se crean cuando se

carga la clase y siguen existiendo mientras ésta se mantenga

cargada.

Las variables miembro existen mientras exista el objeto asociado.

Las variables de clase y variables miembro se inicializan

automáticamente en el momento de crearse. Si el programador no

asigna un valor predeterminado lo hará el compilador.

Las variables no locales (miembro y de clase) pueden utilizar los

siguientes modificadores: public, protected, private, static,

final, transient, volatile.

Declaración y control de acceso (V)

Declaración de un array

Los arrays se usan normalmente para agrupar objetos del mismo

tipo. Permiten hacer referencia al grupo de objetos a través de un

nombre común.

Es posible declarar arrays de cualquier tipo, ya sea de tipos

primitivos o tipos de referencia.

En cualquier caso, un array en Java es un objeto incluso cuando

esté compuesto de tipos primitivos.

Como en el caso de otros tipos de clase, la declaración del array

no crea el objeto en sí. La declaración de un array crea una

referencia al array. La memoria utilizada por los elementos del

array se asigna de forma dinámica mediante una sentencia new o

mediante el inicializador del array.

13

La sintaxis de declaración es:

tipoElementos [] nombreArray;

tipoElementos nombreArray [];

Las dos opciones son válidas. Aunque se suele recomendar los

corchetes a la izquierda por ser más legible y porque éstos se

aplican a todas las variables a la derecha de ellos.

int [] puntuacion, puntuacionMaxima; //Recomendada

int puntuacion[], puntuacionMaxima [];

En la declaración no se debe indicar el tamaño concreto del array,

dará error de compilación. Veremos esto más adelante en la

construcción e incialización de arrays.

Tipos Enumerados

Una práctica habitual en programación es tener un número finito de

nombres simbólicos que representan los valores de un atributo.

Por ejemplo para representar los colores de un semáforo: ROJO,

AMARILLO, VERDE.

A partir de la versión 5.0, Java SE incluye una modalidad de tipos

enumerados que mantiene la seguridad de los tipos.

Veamos un ejemplo de declaración:

1

2

3

4

5

6

7

public class Semaforo {

enum ColorSemaforo{

ROJO,

AMARILLO,

VERDE

};// el punto y coma es opcional en la declaracion de enums.

...

Habría que pensar en el tipo Semaforo como en una clase con un

conjunto finito de valores que reciben los nombres simbólicos

incluidos en la definición del tipo. Los valores serían realmente

instancias de esa clase. El compilador no permitirá que pueda

asignarse otro valor diferente a los especificados.

El orden en el que definamos los valores importa, pero se verá más

adelante

1 ColorSemaforo colorActual = ColorSemaforo.VERDE; // VERDE es

de tipo ColorSemaforo

14

2 ColorSemaforo colorIncorrecto = ColorSemaforo.AZUL;

//provocará error de compilación.

Los tipos enumerados pueden declararse como miembro de una clase,

como una clase aparte, pero nunca dentro de un método.

Declarando constructores, variables y métodos en un Tipo

Enumerado

La nueva modalidad de tipos enumerados permite usar atributos y

métodos, igual que en otras clases.

Un constructor enum siempre debería usar acceso privado. Los

argumentos del constructor se suministran después de cada valor

declarado. Los tipos enumerados pueden tener cualquier cantidad de

atributos y métodos.

Para nuestro ejemplo, definamos un atributo nombreColor con un

literal más amigable que las mayúsculas y un atributo

segundosColor que indicará el tiempo en segundos que está activado

cada color.

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

//Argumentos para el constructor en la declaración

public enum ColorSemaforo{

ROJO ("Rojo", 60),

AMARILLO ("Amarillo", 25),

VERDE ("Verde", 45); //Se requiere el punto y coma cuando

hay

//mas código en la definición del enum

private String nombreColor; //atributos

private int segundosColor; // atributos

//Constructor

private ColorSemaforo (String nombreColor, int

segundosColor){

this.nombreColor = nombreColor;

this.segundosColor = segundosColor;

}

15

18

19

20

21

22

23

24

//Métodos getters

public String getNombreColor(){

return nombreColor;

}

public int getSegundosColor(){

return segundosColor;

}

}

Recordaremos de los constructores de tipos enumerados:

No se puede invocar un constructor de un enum directamente.

Se llama automáticamente cuando especificamos los argumentos

en la declaración de los valores del tipo enumerado.

Se puede definir más de un argumento para el constructor y se

puede sobrecargar los constructores

Orientación a objetos (I)

Encapsulación

La encapsulación es la forma de ocultar ciertos elementos de la

implementación de una clase y, al mismo tiempo, proporcionar una

interfaz pública para el software cliente.

Es una forma más de ocultar los datos, ya que la información de

los atributos es un elemento significativo de la implementación de

las clases. Forzamos a que el código llamante que usa nuestras

clases utilicen métodos para acceder a los atributos en lugar de

usar los atributos directamente.

¿Cómo hacemos esto?

Protegiendo los atributos de nuestras clases (usando el

modificador private)

Suministrando métodos públicos para acceder a nuestros

atributos privados.

Nombrando estos métodos como se nos indica en las reglas de

convención para JavaBeans (getNombreAtributo,

isNombreAtributo, setNombreAtributo)

16

Herencia

Toda clase en Java hereda de la clase Object. De modo que toda

clase que creemos heredará los métodos de Object (equals, clone,

notify…)

Los motivos más usuales para utilizar la herencia son:

Reutilizar código

Poder hacer uso del Polimorfismo

¿Cómo reutilizamos código en la herencia?

En la herencia, al poner los atributos y comportamiento común en

la clase padre estamos evitando repetir este código en cada clase

hija.

Cada subclase definirá solo los atributos y métodos que la hacen

una clase más especializada y de forma automática heredará los

atributos y métodos de la clase padre.

¿Cómo hacemos uso del polimorfismo en la herencia?

Un objeto sólo tiene una forma (aquella que se le asigna cuando se

construye). Sin embargo, una variable es polimórfica porque puede

hacer referencia a objetos de distintas formas.

Java, como la mayoría de los lenguajes de programación orientado a

objetos, permite hacer referencia a un objeto con una variable que

es uno de los tipos de una superclase. Por lo tanto sería posible:

Superclase variable = new Subclase();

Empleado e1 = new Tecnico();

Las variable e1, puede acceder únicamente a las partes del objeto

que son componentes de Empleado; las partes específicas de Tecnico

están ocultas.

Esto es porque, por lo que respecta al compilador, e1 es un

Empleado, no un Gerente.

Usamos el polimorfismo en la herencia cuando en el código no nos

importa qué subclase es, sino que lo que nos importa es que es una

subclase de la clase padre que nos interesa.

Por ejemplo, supongamos un array con objetos de las distintas

subclases de Empleado.

Podríamos recorrer este array volcando cada objeto en una variable

de la superclase y acceder a un método común, getNombreEmpleado().

1

2

3

4

Empleado[] empleados = new Empleado[4];

empleados[0]=new Tecnico();

empleados[1]=new Secretario();

empleados[2]=new Contable();

17

5

6

7

8

9

10

11

12

13

empleados[3]=new Tecnico();

for (int i=0; i<4; i++){

Empleado e = empleados[i];

System.out.println(e.getNombre());

System.out.println(e.getAntiguedad());

System.out.println(e.getSueldo());

}

Relación “es-un”

El concepto es-un se basa en la herencia o en la implementación de

interfaces. Es una forma de decir que “una cosa es un tipo de otra

cosa”.

En Java estas relaciones se expresan mediante las palabras claves

extends o implements.

En el ejemplo anterior cuando Tecnico hereda de Empleado podemos

decir que el “Tecnico es un Empleado”.

Relación “tiene-un”

El concepto tiene-un se basa en la composición. Es decir se

produce cuando para definir una clase estamos usando otra clase.

Por ejemplo, la clase Tecnico podría tener como atributo

proyectoActual de tipo Proyecto. Podemos decir que el “Tecnico

tiene-un Proyecto”.

Sobrescritura de métodos

En las subclases además de poder añadir nuevos atributos para

ampliar la clase padre, es posible modificar un método de la clase

original.

Si se define un método en una subclase de tal forma que su nombre,

el tipo de retorno y la lista de argumentos son idénticos a los

de la clase padre, se dice que el nuevo método sobrescribe al

antiguo.

A partir de la versión J2SE 5.0, también se permite que el tipo de

retorno pueda ser una subclase del tipo de retorno del método

original.

Los métodos sobrescritos no pueden ser menos accesibles

18

El nuevo método no puede ser menos accesible que el método

original. Por ejemplo, si en la clase padre el método que queremos

sobrescribir era public, el método en la clase hija no puede ser

private.

Llamada a métodos sobrescritos

Un método de una subclase puede llamar a un método de una

superclase usando la palabra clave super.

La palabra super se refiere a la superclase de la clase en la que

estamos haciendo uso de ella.

Se usa para hacer referencia a las variables miembro o a los

métodos de la superclase.

Una llamada a super.metodo() llama al comportamiento completo del

método como si hubiera sido llamado en una clase de nivel

superior. El método no tiene que estar definido en la clase

inmediatamente superior, puede heredarse de alguna clase situada

más arriba en la jerarquía.

Sobrescritura de métodos y excepciones

Cuando se sobrescribe un método que genera excepciones, el método

que sobrescribe puede declarar sólo excepciones que sean la misma

clase o una subclase de las excepciones.

Está permitido declarar métodos de sobrescritura que generen menos

excepciones que el método de la superclase, incluso que no generen

ninguna excepción.

Orientación a objetos (II)

Polimorfismo

Hemos hablado anteriormente del polimorfismo cuando hablabamos

sobre la herencia. Decíamos que una variable es polimórfica porque

puede hacer referencia a objetos de distintas formas.

Y vimos que Java permitía hacer referencia a un objeto con una

variable del tipo de una superclase.

Empleado e = new Tecnico();

Usando la variable e podíamos acceder únicamente a las partes del

objeto que son componentes de Empleado (la superclase). Esto se

debía a que para el compilador la variable e es de tipo Empleado y

no Tecnico.

19

Pero supongamos que ambas clases tienen un método toString().

Sabemos que si declaramos lo siguiente y llamamos a toString()

Empleado e1 = new Empleado();

Tecnico e2 = new Tecnico();

e1.toString();

e2.toString();

e1 llama al método toString() de la clase Empleado y e2 llama al

método toString de la clase Tecnico.

¿Pero que ocurre si hacemos esta llamada?

Empleado e3 = new Tecnico();

e3.toString();

Se obtiene el comportamiento asociado al objeto al que hace

referencia la variable durante el tiempo de ejecución. El

comportamiento no está determinado por el tipo de la variable en

el momento de la compilación. Este es un aspecto del polimorfismo

que a menudo recibe el nombre de llamada a métodos virtuales.

e3.toString() llama al método del tipo real del objeto, Tecnico.

Colecciones hetereogéneas

Es posible crear colecciones de objetos que son de una misma

clase. Estas colecciones se llaman homogéneas. Por ejemplo,

cualquier array que construyamos de una clase es una colección

homogénea:

Clientes[ ] clientesNuevos = new Clientes[100];

clientesNuevos[0] = new Cliente();

clientesNuevos[1] = new Cliente();

clientesNuevos[2] = new Cliente();

Sin embargo, Java permite crear colecciones compuestas por objetos

de distintos tipos. Estas colecciones se llaman hetereogéneas.

Por ejemplo, una colección con objetos de las subclases de

Empleado que vimos anteriormente:

Empleado[] empleados = new Empleado[3];

empleados[0] = new Tecnico();

empleados[1] = new Secretario();

empleados[2] = new Contable();

Es incluso posible hacer una colección con objetos de cualquier

clase, ya que en Java todos los objetos heredan de Object. Podemos

tener una colección como esta:

Object [] objetos = new Object[5];

objetos[0] = new Empleado();

20

objetos[1] = new Batman();

objetos[2] = new Libro();

objetos[3] = new String(“Maria”);

objetos[4] = new Bicicleta();

Podríamos recorrer el array y llamar a un método que fuera común,

por ejemplo toString().

Sobrecarga de métodos

En algunos casos podemos necesitar definir varios métodos que

realicen la misma función pero con diferentes parámetros. Java

permite reutilizar un mismo nombre de método para varios métodos

si al llamarlos es posible distinguir unos y otros.

Reglas de los métodos sobrecargados:

Las listas de argumentos deben ser diferentes. Deben poder

determinar sin ambigüedad qué método es el que se quiere

llamar.

Los tipos de retorno pueden ser diferentes. El tipo de

retorno puede ser diferente pero no es suficiente si ésta es

la única diferencia.

El modificador de acceso puede cambiar.

Puede declarar nuevas o más amplias excepciones comprobadas.

Y a tener en cuenta:

Un método se puede sobrecargar en la misma clase o en una

subclase.

Llamadas a métodos sobrecargados

Cuando llamamos a un método sobrecargado, existe más de un método

con el mismo nombre. Decidir qué método es el que se debe llamar

se hace en base a los argumentos.

Si tenemos un método sumar(int a, int b), y otro método

sumar(double a, double b).

La llamada sumar(10, 7) llama a sumar con argumentos enteros y la

llamada sumar(2.7, 2.3) llama a sumar con argumentos double.

¿Pero qué ocurre cuando en lugar de parámetros de tipos primitivos

tenemos parámetros de tipos de referencia?

Supongamos estos métodos sobrecargados de la clase Proyecto.

public void añadirPersonal (Empleado e);

public void añadirPersonal (Tecnico t);

Con estas llamadas:

Empleado e = new Empleado();

21

Tecnico t = new Tecnico();

Empleado tec = new Tecnico();

e.añadirPersonal(e); //Como esperamos e es de tipo Empleado y

llama a añadirPersonal(Empleado e)

e.añadirPersonal(t); //Como es de esperar, t es de tipo Tecnico y

llama a añadirPersonal(Tecnico t)

e.añadirPersonal(tec); //En este caso tec es una referencia de

Empleado pero es en realidad un objeto Tecnico.

En este ultimo caso se llama al método añadirPersonal(Empleado e).

Aunque el objeto en tiempo de ejecución sea un Tecnico, la

decisión de a qué método llamar no se hace dinámicamente en tiempo

de ejecución, asi que el tipo de referencia determina a qué metodo

sobrecargado se llama

22

Orientación a objetos (III)

Sobrecarga Constructores

Sobrecarga de constructores

Una clase puede definir varios constructores para permitir

inicializar un objeto con más o menos atributos.

Podríamos tener un constructor sin argumentos para crear un objeto

con unos atributos por defecto y varios constructores con

argumentos dependiendo de a qué atributos queramos dar valor.

Por ejemplo, para la clase Tecnico:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

public class Tecnico{

public static final double SALARIO_BASE = 600;

private String nombre;

private int departamento;

private float sueldo;

public Tecnico(){ //Constructor sin parámetros.

nombre = "";

departamento = 0;

sueldo = SALARIO_BASE;

}

public Tecnico(String nombre, int departamento, float

sueldo){ //Constructor con todos los parámetros.

this.nombre = nombre;

this.departamento = departamento;

this.sueldo = sueldo;

}

public Tecnico(String nombre, float sueldo){ //Constructor con tres

parámetros, que utiliza this().

this(nombre, 0, sueldo);

}

23

21

22

}

El primer constructor sin parámetros inicializa todos los

atributos con valores por defecto para nombre, departamento y

sueldo.

El segundo constructor tiene parámetros para cada atributo de

Tecnico.

El tercer constructor tiene dos parámetros, nombre y sueldo. La

referencia this se utiliza como forma de envío de llamada a otro

constructor (siempre dentro de la misma clase), en este caso el

segundo constructor.

Es decir en esta llamada:

Tecnico t3 = new Tecnico (“Jose Luis”, 1300.50f);

Estaremos invocando al constructor con tres parámetros

así: Tecnico(“Jose Luis”, 0, 1300.50f)

Los constructores no se heredan

Aunque las subclases heredan todos los métodos y variables de sus

superclases, no heredan sus constructores.

Las clases sólo pueden obtener un constructor de dos formas: debe

escribirlo el programador o, si éste no lo escribe, debe usar el

constructor predeterminado. Siempre se llama al constructor del

nivel superior además de llamar al constructor subordinado.

Llamada a constructores de nivel superior

Puede llamar a un determinado constructor de una superclase como

parte de la inicialización de una subclase utilizando la palabra

clave super en la primera línea del constructor de la subclase. Es

preciso suministrar los argumentos adecuados a super().

Cuando no hay ninguna llamada a super con argumentos, se llama

implícitamente al constructor de la superclase que tenga cero

argumentos. Si en la superclase no hubiera un constructor sin

argumentos se produciría un error de compilación.

La llamada super() puede adoptar cualquier cantidad de argumentos

adecuados para los distintos constructores disponibles en la

superclase, pero debe ser la primera sentencia del constructor.

Recordar:

Si se utilizan, es preciso que super o this sean la primera

línea del constructor.

24

Si un constructor no contiene ninguna llamada a super(…) ni a

this(…), el compilador introduce una llamada al constructor

de la superclase sin argumentos.

Otros constructores pueden llamar también a super(…) o

this(…), con lo que iríamos encadenando llamadas a

constructores. Al final el constructor de la clase de nivel

superior se ejecutará antes que ningún otro constructor

subordinado.

Un constructor nunca puede tener ambas llamadas super(…) y

this(…).

Orientación a objetos (IV) – Conversiones

de tipos

Operador instanceof

Como hemos visto, es posible utilizar objetos mediante referencias

a sus superclases. Por lo tanto, a veces es necesario saber con

qué objetos reales estamos tratando. Para ello tenemos el operador

instanceof.

Por ejemplo, si tenemos la siguiente jerarquía de clases,

utilizaríamos el operador instanceof para realizar unas acciones u

otras dependiendo del objeto que realmente tengamos:

1 public class Contrato{...}

1 public class Deposito extends Contrato{...}

1 public class Prestamo extends Contrato{...}

1

2

3

4

public void finalizarContrato(Contrato c){

if (c instanceof Deposito){

//Devolver intereses

//Poner indicador de contrato vivo a falso

25

5

6

7

8

9

10

}

else if (c instanceof Prestamo){

//Poner importePendiente a 0

//Poner indicador de contrato vivo a falso

}

}

¿Como podemos realizar estas acciones si para nuestro compilador c

es de tipo Contrato y no podríamos acceder a métodos de Deposito y

de Prestamo? Haciendo una conversión de tipos.

Conversión de tipos

En los casos como el anterior, en el que ya hemos comprobado

mediante el operador instanceof que el objeto es una subclase

concreta, es posible acceder a toda la funcionalidad del objeto

convirtiendo la referencia.

Para el ejemplo anterior sería:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

public void finalizarContrato(Contrato c){

if (c instanceof Deposito){

Deposito d = (Deposito) c;

//Devolver intereses

d.devolverIntereses();

//Poner indicador de contrato vivo a falso

d.setIndContratoVivo(false);

}

else if (c instanceof Prestamo){

Prestamo p = (Prestamo)c;

//Poner importePendiente a 0

p.setImportePendiente(0);

//Poner indicador de contrato vivo a falso

p.setIndContratoVivo(false);

}

}

26

Si no realizamos la conversión, cualquier llamada a un método

provocará error al no localizar tal método en la superclase.

Debemos tener en cuenta:

Las conversiones en dirección ascendente (de un objeto de una

subclase a una referencia a una superclase o interfaz)

siempre están permitidas, y de hecho, no precisan el operador

de conversión. Se pueden hacer mediante una simple

asignación.

1

2

Prestamo pr = new Prestamo();

Contrato c = pr; //"Padre" = "Hijo" siempre posible y no

necesario cast.

Si Prestamo implementa Firmable con el método firmar, también

es legal la asignación directa:

1

2

3

4

Prestamo pr = new Prestamo();

Firmable f = prestamo;

f.firmar(); //podremos llamar a los métodos de la interfaz,

//ya que están implementados en Prestamo.

Y más aún, si tenemos la clase PrestamoHipotecario que hereda

de Prestamo, que implementa a Firmable, también podemos hacer

la asignación directa. Ya que PrestamoHipotecario hereda

también la función Firmar por Prestamo implementar a

Firmable.

1

2

3

PrestamoHipotecario hip = new PrestamoHipotecario();

Firmable f = hip;

f.firmar();

En las conversiones de dirección descendente (de un objeto de

una superclase a una referencia a una subclase), el

compilador debe poder considerar que la conversión es

posible, para esto, la clase destino de la conversión tiene

que ser una subclase del tipo de referencia actual.

1

2

3

Contrato c = new Contrato();

Prestamo p = (Prestamo) c; // "Hijo" = "Padre" necesita

cast.

27

4 //El compilador no dará fallo pues parece posible la

conversión.

//Prestamo es una subclase de Contrato.

1

2

3

4

Empleado e = new Empleado();

Prestamo p = (Prestamo) e; //En este caso el compilador

considera

//que es imposible la conversión. Ni siquiera está en la

jerarquía.

//Provocará un error 'Inconvertible types'

Si el compilador permite la conversión, todavía no podemos

cantar victoria, el tipo de objeto se comprueba durante el

tiempo de ejecución. Si el objeto que se va a convertir no es

realmente del tipo al que se hace la conversión, se producirá

un error en tiempo de ejecución. Será una excepción

ClassCastException.

1

2

3

4

5

Contrato c1 = new Contrato();

Contrato c2 = new Prestamo();

Prestamo p1 = (Prestamo) c1; //Compila pero genera

ClassCastException al ejecutar.

Prestamo p2 = (Prestamo) c2; //Compila y se ejecuta

correctamente.

Orientación a objetos (V) – Implementación de

interfaces y tipos de retorno.

Implementando Interfaces

Decíamos con anterioridad que las interfaces son una especie de

“contrato”, una declaración del conjunto de métodos que podemos

utilizar si una clase implementa dicha interfaz.

Por tanto, una clase al implementar una interfaz debe definir cada

uno de los métodos de ésta. De esta forma, cualquier persona que

instancie un objeto de la clase implementadora tiene la seguridad

de que puede llamar a los métodos de la interfaz.

28

Cuando Java encuentra una clase que dice implementar una interfaz

la comprueba. Si falta algún método por definir nos dará error de

compilación. Basta que tengamos la definición de los métodos

aunque no tengamos ningún código para ellos.

Por ejemplo, esta clase no daría ningún error:

1

2

3

4

5

6

7

8

9

10

11

12

13

public interface Cultivable{

public void plantar();

public void regar();

public int cosechar();

}

public class cultivoFresas implements Cultivable {

....

public void plantar() {}

public void regar(){}

public int cosechar(){}

...

}

También es posible que nuestra clase implementadora sea abstracta.

En este caso la clase puede elegir entre implementar algunos

métodos, todos o ninguno. Y sería alguna clase concreta de ésta la

que defina los restantes.

Por ejemplo:

1

2

3

4

5

public abstract class Cultivos implements Cultivable {

public void plantar() { ... } //Ejemplo de clase abstracta

public void regar() { ... } // que define algunos métodos

}

29

6

7

8

public class Maiz extends Cultivos {

public void int cosechar() { ... } //Y el que falta en la

concreta.

}

Las clases que implementan una interfaz deben seguir las misas

normas que las clases que extienden una clase abstracta:

Deben definir todos los métodos de la interfaz.

Seguir todas las normas para las sobrescrituras (overrides).

Declarar sólo las excepciones comprobadas definidas en la

interfaz o subclases de éstas.

Mantener la signatura de los métodos y el tipo devuelto (o un

subtipo de éste).

Hay que recordar también que:

Una clase puede extender a sólo una clase.

Una clase puede implementar varias interfaces.

Una interfaz puede extender una o varias interfaces, pero

nunca puede implementar.

Tipos de Retorno Legales

Vamos a hacer un pequeño resumen de qué podemos declarar como

tipos de retorno:

Métodos sobrecargados: un método sobrecargado es un método

totalmente diferente y nuevo.

Si tenemos un método en una clase y lo sobrecargamos en una

subclase no tiene que regirse por las normas de los métodos

sobrescritos. Podemos declarar cualquier tipo de retorno.Hay

que recordar que la sobrecarga no sólo se puede basar en un

tipo diferente de retorno, debía diferenciarse del método

original por el número o tipo de parámetros.

Métodos sobrescritos: un método sobrescrito consistía en dar

una implementación distinta a un método en una subclase. El

método debía coincidir exactamente. A partir de Java 5, se

permite también que el tipo de retorno en el método

30

sobrescrito sea una subclase del tipo de retorno del método

en la superclase.

Para devolver correctamente un valor para que se ajuste al tipo de

retorno, hay que tener en cuenta:

Sólo podemos devolver null si el tipo de retorno es una

variable de referencia (objeto y no tipo primitivo).

Si el tipo de retorno es un tipo primitivo cualquier valor

que sea del tipo o que se pueda convertir

implícitamente o explícitamente (usando un cast).

Nunca retornar un valor si el tipo de retorno es void.

Si el tipo de retorno es una variable de referencia (objeto y

no tipo primitivo) podemos devolver un objeto de una

subclase, ya que se hace el cast implícitamente.

Si el tipo de retorno es una interfaz podemos devolver

cualquier objeto de una clase que implemente dicha interfaz.

Orientación a Objetos (VI) – Constructores

Constructores

Los constructores son métodos especiales que se utilizan para

inicializar un objeto de una clase.

Los constructores son la única forma de crear un objeto en Java.

El nombre del constructor debe ser idéntico al de la clase y no

tendrá tipo de retorno. Los modificadores de acceso válidos para

los constructores son: public, protected y private.

Una clase puede tener cualquier número de constructores. Todos

tendrán el mismo nombre y al igual que otros métodos

sobrecargados, los constructores se diferenciaran unos de otros en

el número y en el tipo de sus parámetros.

La sintaxis será:

[modificador acceso] nombreClase ([listaParámetros]) {…}

Encadenamiento de Constructores

Para llamar al constructor utilizaremos la palabra clave new.

Además, con esta palabra clave, no sólo estamos invocando al

constructor de la clase de nuestro objeto, sino también a los

constructores de las superclases.

Por ejemplo, imaginemos que tenemos la siguiente clase:

31

1

2

3

public class Solicitante extends Persona extends Object {

...

}

Al crear un objeto con new,

1

2

3

...

Solicitante solAyuda1 = new Solicitante();

...

se llamará al constructor de Solicitante, que llamará al

constructor de Persona, que llamará al constructor de Object.

Después, terminará el constructor de Object, terminará el

constructor de Persona inicializando los atributos que le

correspondan, y terminará el constructor de Solicitante

inicializando los atributos que le correspondan.

El Constructor es Obligatorio

Todas las clases deben tener un constructor, incluso las clases

abstractas.

Si no escribimos un constructor para una clase el compilador añade

uno por defecto.

En este caso, el constructor no tendrá parámetros, su cuerpo

estará vacío y tendrá el mismo modificador de acceso que la clase.

Por ejemplo, para esta clase que no hemos escrito constructor:

1

2

3

4

5

public class Persona{

private String id;

private String nombre;

private String movil;

}

El compilador insertaría:

1

2

3

4

public class Persona{

private String id;

private String nombre;

private String movil;

32

5

6

public Persona(){}

}

En el momento en que escribamos un constructor (con o sin

parámetros) el constructor por defecto se perderá.

A Recordar

Cada constructor debe tener en su primera línea una llamada a

otro constructor sobrecargado, (this) o una llamada al

constructor de la superclase (super).

En el caso de que no escribamos la llamada a super, el

compilador insertará automáticamente una llamada a super sin

parámetros, super().

Sólo es posible llamar a a un constructor desde otro

constructor.

Las interfaces no pueden tener constructores, ya que no son

clases.

La llamada al constructor de la superclase mediante super

puede ser con o sin parámetros.

Si el constructor de la superclase necesita parámetros y en

el constructor de la subclase hacemos una llamada a super sin

parámetros fallará.

Las clases abstractas también tienen constructores. Estos

constructores serán llamados cuando se ejecute el constructor

de las subclases que la concretan.

Orientación a Objetos (VII) – Miembros estáticos

Static

La palabra clave static declara miembros (atributos, métodos y

clases anidadas) que están asociados a la clase en lugar de a una

instancia de la clase.

Utilidad

A veces puede ser útil tener una variable compartida por todos los

objetos de la clase (algo parecido a una variable global). En este

caso marcaríamos esta variable como static.

Si una variable static no se marca como private, es posible

acceder a ella desde fuera de la clase. Para hacerlo, no se

necesita ningún objeto, se hace referencia a ella mediante el

nombre de la clase.

33

Por ejemplo, un caso común es un contador de los objetos

instanciados de una clase:

1

2

3

4

5

6

7

8

9

10

11

12

public class Leccion{

public static int contador; //variable estática, asociada a la

clase

private int idLeccion; //atributos, variables no estáticas

private String desLeccion; //asociadas a los objetos

public Leccion()

{

contador = contador + 1; //podemos usar el contador para el

id de cada objeto Leccion.

idLeccion = contador;

desLeccion = "Sin nombre";

}

}

1

2

3

4

5

6

7

8

9

public class MainLeccion{

public static void main (String[] args){

System.out.println("Contador: " + Leccion.contador);

//Muestra 0

Leccion l1 = new Leccion();

Leccion l2 = new Leccion();

System.out.println("Contador: " + Leccion.contador);

//Muestra 2

}

}

O cuando necesitemos un método cuyo código no dependa de los

atributos de un objeto de la clase. En este caso marcaríamos este

método como static.

34

Por ejemplo, podríamos hacer la variable estática anterior privada

y obtener el número de objetos Leccion creados mediante un método

getContador().

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

public class Leccion{

private static int contador; //variable estática, asociada a la

clase

public static int getContador(){ //método estático

return contador;

}

private int idLeccion; //atributos, variables no estáticas

private String desLeccion; //asociadas a los objetos

public Leccion()

{

contador = contador + 1; //podemos usar el contador para el

id de cada objeto Leccion.

idLeccion = contador;

desLeccion = "Sin nombre";

}

}

En la clase principal ahora llamaríamos al método estático:

1

2

3

4

5

6

7

public class MainLeccion{

public static void main (String[] args){

System.out.println("Contador: " +

Leccion.getContador()); //Muestra 0

Leccion l1 = new Leccion();

Leccion l2 = new Leccion();

System.out.println("Contador: " +

Leccion.getContador()); //Muestra 2

}

35

Accediendo a Miembros Estáticos

Anteriormente hemos visto que para acceder a las variables y

métodos estáticos hemos utilizado el nombre de la clase y el

operador punto. Es lo más lógico dado que las variables y métodos

estáticos pertenecen a la clase, pero Java permite también acceder

a los miembros estáticos utilizando un objeto.

Java lo permite pero el compilador realmente lo sustituirá por el

nombre de la clase.

En el ejemplo anterior podíamos haber escrito:

1 System.out.println("Contador: " + l2.getContador());

Esto último solo lo podremos hacer si existe una instancia de la

clase.

Redefinición en Métodos Estáticos

Los métodos estáticos no se pueden sobrescribir (override), sin

embargo si es posible tener el mismo método en una subclase. Esto

se denomina redefinición o ocultación.

Si se llama al método con una referencia de objeto, el método

llamado será el correspondiente a la clase para la que se haya

declarado la variable.

¿Qué diferencia existe entre la sobrescritura y la redefinición?

La sobrescritura (override) está íntimamente ligada al

polimorfismo. Como los métodos estáticos están asociados a la

clase en lugar de a los objetos, el polimorfismo en tiempo de

ejecución no es posible y por lo tanto no es sobrescritura sino

redefinición.

Veamos la diferencia. Creamos una clase Persona y una subclase

Cliente. Creamos un método no estático, mostrar() y un método

estático, mostrarStatic() en cada una de las clases.

1

2

3

4

5

6

public class Persona{

private String nif;

private String nombre;

private int edad;

public Persona(String nif, String nombre, int edad){

36

7

8

9

10

11

12

13

14

15

16

17

18

19

this.nif = nif;

this.nombre = nombre;

this.edad = edad;

}

... //getters, setters

public void mostrar(){

System.out.println("Persona " + nif + ": \n Nombre: " +

nombre + "\n Edad: " + edad);

}

public static void mostrarStatic(){

System.out.println("Clase Persona");

}

}

1

2

3

4

5

6

7

8

9

10

11

12

13

14

public class Cliente extends Persona{

private int id;

private int antiguedad;

private int descuento;

public Cliente(String nif, String nombre, int edad, int id, int

antiguedad, int descuento) {

super(nif, nombre, edad);

this.id = id;

this.antiguedad = antiguedad;

this.descuento = descuento;

}

... // getters, setters

public void mostrar(){

37

15

16

17

18

19

20

21

System.out.println("Cliente" + id + ": \n Nombre: " +

getNombre()

+ "\n Antigüedad: " + antiguedad + "\n Descuento: " +

descuento);

}

public static void mostrarStatic(){

System.out.println("Clase Cliente");

}

}

Con esta definición de clases si creamos los siguientes objetos

tendríamos los siguientes resultados:

1

2

3

4

5

6

7

8

9

10

11

public class MainSobrescritura{

public static void main(String[] args){

Persona per = new Persona("18520147L", "Maria

Victoria Rodriguez", 18);

Cliente cli = new Cliente("20415789M", "Secundino

Jimenez", 25, 504, 5, 1);

Persona per2 = new Cliente("41605788R", "Luis

Martin", 30, 905, 3, 2);

per.mostrar(); //llama a mostrar() de Persona

cli.mostrar(); //llama a mostrar() de Cliente

per2.mostrar();//llama a mostrar() de Cliente

}

}

Ahora veamos lo que ocurre con un método estático:

1

2

3

4

5

public class MainRedefinicion{

public static void main(String[] args){

Persona per = new Persona("18520147L", "Maria Victoria

Rodriguez", 18);

38

6

7

8

9

10

11

Cliente cli = new Cliente("20415789M", "Secundino Jimenez",

25, 504, 5, 1);

Persona per2 = new Cliente("41605788R", "Luis Martin", 30,

905, 3, 2);

per.mostrarStatic(); //llama a mostrarStatic de Persona

cli.mostrarStatic(); //llama a mostrarStatic de Cliente

per2.mostrarStatic();//llama a mostrarStatic de Persona y

no de Cliente como arriba.

}

}

Vemos que en los métodos estáticos el método llamado será el de la

clase declarada en tiempo de compilación y no en tiempo de

ejecución como pasa con los métodos no estáticos sobrescritos.

Acceso en un método estático

Un método estático no puede acceder a ninguna variable salvo a las

variables locales, los atributos static y sus parámetros.

Cualquier intento de acceder a atributos que no sean estáticos

directamente (sin crear un objeto) provoca un error de

compilación.

Nota El método main es también un método estático. Podemos

comprobar que no creamos ningún objeto para ejecutarlo.Si necesita

datos miembro se deben crear objetos dentro de main.

Orientación a Objetos (VIII) – Acoplamiento

y Cohesión

Uno de los objetivos más importantes del diseño orientado a

objetos es conseguir una alta cohesión entre clases y un bajo

acoplamiento.

¿Qué es la cohesión?

La medida que indica si una clase tiene una función bien definida

dentro del sistema. El objetivo es enfocar de la forma más precisa

posible el propósito de la clase. Cuanto más enfoquemos el

propósito de la clase, mayor será su cohesión.

39

Una prueba fácil de cohesión consiste en examinar una clase y

decidir si todo su contenido está directamente relacionado con el

nombre de la clase y descrito por el mismo.

Una alta cohesión hace más fácil:

Entender qué hace una clase o método

Usar nombres descriptivos

Reutilizar clases o métodos

¿Qué es el acoplamiento?

El acoplamiento entre clases es una medida de la interconexión o

dependencia entre esas clases.

El acoplamiento fuerte significa que las clases relacionadas

necesitan saber detalles internos unas de otras, los cambios se

propagan por el sistema y el sistema es posiblemente más difícil

de entender.

Por ello deberemos siempre intentar que nuestras clases tengan un

acoplamiento bajo. Cuantas menos cosas conozca la clase A sobre la

clase B, menor será su acoplamiento.

Lo ideal es conseguir que la clase A sólo conozca de la clase B lo

necesario para que pueda hacer uso de los métodos de la clase B,

pero no conozca nada acerca de cómo estos métodos o sus atributos

están implementados.

Los atributos de una clase deberán ser privados y la única forma

de acceder a ellos debe ser a través de los métodos getter y

setter.

Un bajo acoplamiento permite:

Entender una clase sin leer otras

Cambiar una clase sin afectar a otras

Mejora la mantenibilidad del código

Asignaciones – Heap y Stack.

El Heap (Montículo) y el Stack (Pila) son diferentes memorias que

utiliza la Java Virtual Machine de Java.

Mientras que para cada thread en la JVM se tiene un Stack privado,

el Heap es un espacio de memoria dinámica único que se crea al

inicio de la máquina virtual. El administrador del Heap es el

sistema de administración de almacenamiento automático o más

conocido como Garbage Collector.

40

¿Qué se guarda en el Stack?

- variables locales

- variables de referencia

- parámetros y valores de retorno

- resultados parciales

- el control de la invocación y retorno de métodos

¿Qué se guarda en el Heap?

- objetos

- variables de instancia

Ejemplo:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

public class EjemploStackYHeap{

int atributoEntero;

String atributoCadena;

public void setAtributoEntero (int x) {

atributoEntero = x;

}

public void setAtributoCadena(String s) {

atributoCadena = s;

}

public static void main (String[] args){

int enteroLocal = 5;

String cadenaLocal = "atributo";

EjemploStackAndHeap e = new EjemploStackAndHeap();

e.setAtributoEntero (enteroLocal);

e.setAtributoCadena (cadenaLocal);

}

}

Comienza la ejecución con main.

Se declara e inicializa una variable de tipo primitivo,

41

enteroLocal.

Se almacena esta variable y su valor en el Stack.

Se declara e inicializa una variable de tipo de referencia.

La variable se crea en el Stack. El objeto se crea en el Heap.

La variable apunta al objeto String en el Heap.

Se llama al constructor EjemploStackAndHeap(). El constructor crea

en el Heap el espacio para el objeto y sus atributos. Se

inicializan los valores según el tipo de los atributos. En el

Stack se crea la variable de referencia e que apunta al objeto en

Heap.

Se llama a setAtributoEntero con el parámetro enteroLocal. La

variable del método setAtributoEntero x recibe el valor de

enteroLocal. Se le asigna el valor de x a atributoEntero. (El

método tiene una variable this que apunta al objeto, de esta forma

puede acceder a sus atributos).

Se llama a setAtributoCadena con el parámetro cadenaLocal. La

variable del método setAtributoCadena s recibe el valor de

cadenaLocal. Se le asigna el valor de s a atributoCadena. (El

método tiene una variable this que apunta al objeto, de esta forma

puede acceder a sus atributos).

42

Asignaciones – Asignaciones, Widening, Cast

La operación de asignación nos

sirve para dar valor a una

variable.

La forma genérica de una

asignación es la siguiente:

variable = valor;

Donde valor puede ser un literal,

otra variable o una expresión.

El proceso de la asignación se

realiza en dos fases. En primer

lugar, se evalúa la parte derecha y se obtiene un valor. En

segundo lugar, se asigna ese valor a la variable de la parte

izquierda.

Literales

Ya comentamos que tenemos ocho tipos primitivos en Java: byte,

short, int, long, float, double, char y boolean. Un literal es un

valor concreto de un tipo.

Enteros

Se pueden representar utilizando una notación decimal, octal o

hexadecimal.

15 formato decimal.

017 formato octal, el prefijo 0 indica un valor octal.

0xF formato hexadecimal, el prefijo 0x indica un valor

hexadecimal.

Un literal entero se considera por defecto de tipo int. Para

indicar que queremos que sea de tipo long debemos acompañarlo de

‘l’ o ‘L’. Algunos literales enteros: 15, 320, 29999, 130000,

250L.

Reales

Un literal real se considera por defecto de tipo double. Para

indicar que queremos que sea de tipo float debemos acompañarlo de

‘f’ o ‘F’. Algunos literales reales en coma flotante: 3.4,

1293.239, 150.23F.

Carácter

Un literal char se escribe entre comillas simples. Como son

carácteres Unicode también se puede especificar el carácter en

concreto por sus dígitos hexadecimales.

Algunos literales char: ‘b’, ‘z’, ‘\n’, ‘\t’, ‘\u003B’

43

Lógico

Los literales son false y true.

Expresiones

Es frecuente que nos encontremos expresiones en la que participan

diferentes tipos de datos. Algunos tipos de datos pueden ser

compatibles (enteros con caracteres p.e), mientras que otros no

(enteros con reales).

Los booleanos no son compatibles con ningún otro tipo de dato.

En las expresiones con datos enteros el resultado será al menos un

int o el tipo del mayor operando de la expresión.

1

2

3

byte a = 10;

byte b = 20;

byte c = (a + b) * 0.5; //El resultado de (a+b) * 0.5 es de

tipo double.

En este caso el compilador generará un error, se necesita una

conversión.

En Java las conversiones pueden ser implícitas o explícitas.

Conversión Implícita (Widening)

Se produce cuando los tipos son compatibles y Java realiza la

conversión de forma automática.

Siempre es posible asignar un valor de un tipo más pequeño en una

variable de un tipo mayor.

Grande = Pequeño; //OK

1 long a = 100; //100 al ser un literal entero, es de tipo

int.

No hay problema en guardar este valor en una variable long. La

conversión es automática.

Conversión Explícita (Cast)

Cuando los tipos son incompatibles o existe la posibilidad de

perder información en una asignación, el compilador dará un error

pidiendo que se confirme esa asignación con una conversión

explícita.

Pequeño = Grande; //ERROR

44

1

2

int a = 100L; //ERROR 100L es un literal de tipo long y se quiere

asignar a una variable int.

int a = (int) 100L; // OK

El compilador generará error. Como sabemos que en la asignación no

hay peligro de perder información al

caber 100 perfectamente en un int, convertimos explícitamente.

Casos Posibles de Asignaciones

o Asignación de Iguales: Se produce cuando el tipo del

valor a asignar coincide con el tipo de la variable. En

esta situación no hay ningún problema.

1

2

3

4

5

char c = 'a';

int entero = 100;

int suma = entero + 25;

double precio = 950.283;

boolean indCerrado = false;

En estas asignaciones es necesario ningún tipo de conversión.

o Conversión de ampliación (Widening): Ocurre cuando el

tipo del valor a asignar es más pequeño que el tipo de

la variable.

1

2

3

4

//grande = pequeño; //OK

int a = 150; //Declaramos un entero

long var = a; //La variable a se puede asignar al long var, ya que

vamos de pequeño a grande.

double b = 15.3f * var;

Cualquier expresión entera siempre se podrá asignar a una variable

double, ya que es el mayor tipo entero.

Conversión de reducción (Narrowing): Ocurre cuando el tipo

del valor a asignar es más grande que el tipo de la variable.

Es necesaria la conversión explícita.

1

2

//pequeño = grande; //ERROR

//pequeño = (pequeño) grande; //OK

45

3

4

5

6

7

8

9

10

long grande = 5500L;

int pequeño = (int) grande; //Es necesario hacer un casting a int.

byte primero = 5;

byte segundo = 20;

byte suma = (byte) (primero + segundo); //La suma de dos bytes es un

int, por lo tanto necesita

//un casting a byte.

o Excepción:

Cuando el valor a asignar es una expresión onstante de

tipo byte, short, char o int el

compilador hace la conversión automáticamente. Esto se

llama compile-time narrowing of constants:

1 byte auto = 10; //OK, no hace falta hacer byte auto =

(byte) 10;

o Si la variable a asignar es final y “cabe” en el destino

no es necesario casting:

1

2

3

4

5

final short prueba = 100;

byte destino = prueba; //OK

final short prueba = 300; //sobrepasa el valor posible

para los byte.

byte destino = prueba; //ERROR: possible loss of

precision

o Nota:

Aunque tengamos un valor real que pueda caber en un

“float” y lo queremos asignar a un

double, siempre será necesario el sufijo o el casting:

1

2

3

float f = 500.7; //ERROR

float f = 500.7f; //OK

float f = (float) 500.7; //OK

46

Conversión de Tipos de Referencia:Se puede asignar a un tipo

menos especializado un tipo más especializado en la

jerarquía.Padre = Hijo //OK

1 Persona pe = new Programador();

Esto es posible porque un objeto hijo (más especializado)

puede hacer todo lo que haga el

padre (menos especializado) pero no al revés.

Hijo = Padre //ERROR

1 Programador pro = new Persona(); //ERROR

Asignaciones – Paso de Parámetros en Java

El paso de parámetros en Java se hace siempre por valor, ya se

trate de un tipo primitivo o de un tipo de referencia, el método

trabaja con una copia de la variable.

En el caso de una variable de tipo primitivo, si dentro del método

se modifica la variable, estamos modificando la copia y no la

variable original.

En el caso de variables de referencia, si se modifica la variable

dentro del método se está modificando una copia de la referencia y

no la referencia original. Eso sí, al tener una copia de la

referencia que apunta al mismo objeto, sí se puede cambiar el

contenido de éste.

Veámoslo con un ejemplo:

1

2

3

4

5

6

7

8

class PasoParametros{

public static void main(String[] args){

//Para un entero comprobamos que se modifica la copia,

despues vuelve a su valor orig.

int entero = 100;

System.out.println("Antes de modificar: " + entero);

47

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

modificar(entero);

System.out.println("Despues de modificar: " + entero);

//Para un array de un entero, comprobamos que con la copia de

la referencia

//podemos cambiar su contenido.

int[] array = new int[1];

array[0] = 100;

System.out.println("\nAntes de modificar: " + array[0]);

modificarContenido(array);

System.out.println("Despues de modificar: " + array[0]);

//Para el mismo array de entero, comprobamos que no podemos

modificar la referencia.

System.out.println("\nAntes de modificar: " + array[0]);

modificar(array);

System.out.println("Despues de modificar: " + array[0]);

}

static void modificar(int e){

e = 200;

System.out.println("Dentro de modificar: " + e);

}

static void modificarContenido(int[] a){

a[0] = 200;

System.out.println("Dentro de modificarContenido: " +

a[0]);

}

48

38

39

40

41

42

43

44

45

static void modificar (int[] a){

//Creamos un nuevo array "b" y hacemos que "a" apunte a este

nuevo array.

//Funciona solo dentro del metodo, puesto que "a" aquí es una

copia.

int[] b = new int[1];

b[0] = 300;

a = b;

System.out.println("Dentro de modificar: " + a[0]);

}

}

Resultado de la ejecución:

49

Asignaciones – Arrays

Un array es un objeto que nos permite guardar varios elementos de

un mismo tipo. Un array puede guardar valores de tipo primitivo o

de tipo de referencia, pero el array siempre será un objeto y como

tal se almacena en la memoria dinámica, el Heap.

Los arrays pueden ser unidimensionales (vectores) o

multidimensionales (matrices).

En la imagen superior tenemos un ejemplo de un array

unidimensional de longitud n. El índice de los arrays en Java

siempre empiezan en 0, por tanto la última posición será n-1. El

índice nos permite acceder a un elemento determinado del array.

En los arrays multidimensionales tendremos tantos índices como

dimensiones.

Declaración de un array

Un array se declara especificando el tipo del array a la variable:

char[] arrayCaracteres;

char arrayVocales []; //posición de los corchetes legal

pero menos legible.

String[] arrayCadenas;

int[] cuponOnce;

int[][] matriz;

La declaración del array crea una referencia que apuntará al

array.

La declaración nunca debe indicar el tamaño del array.

-

Creación de un array

arrayCaracteres = new char[20];

arrayVocales = new char[5];

arrayCadenas = new String[3];

Después de su creación los arrays se inicializan con el valor

predeterminado de su tipo.

50

Aunque hemos separado la declaración de la construcción del array,

lo más usual es que la declaración y la construcción se hagan en

una misma línea:

int[] notasTrimestres = new int[4];

Java también permite utilizar una forma abreviada para crear

arrays con valores iniciales dando los valores entre llaves:

char[] letras = {‘a’, ‘b’, ‘c’, ‘d’};

String[] nombres = {“María”, “Jose”, “Alberto”}

-

Arrays multidimensionales

Dado que es posible declarar un array de cualquier tipo, los

arrays multidimensionales en Java se crean mediante arrays de

arrays.

Un array bidimensional:

//Crea un array de tres elementos en los que cada uno de ellos es

un array.

int [][] bidimensional = new int [3][];

Ahora podemos dar a cada uno de estos tres arrays dimensiones

diferentes si queremos:

bidimensional[0] = new int[2];

bidimensional[1] = new int[3];

bidimensional[2] = new int[2];

51

Si todos nuestros arrays de arrays van a tener la misma dimensión

podemos ahorrarnos esfuerzo y se permite la siguiente declaración

y construcción:

int [][] rectangular = new int[3][2]

-

Límites

En Java todos los arrays comienzan en 0. El número de elementos de

un array se guarda en el

atributo del array length. Es recomendable utilizar este atributo

cuando iteremos los elementos de un array para evitarnos acceder

fuera de los límites permitidos.

Si intentamos acceder a una posición incorrecta del array se

generará la excepción ArrayIndexOutOfBoundsException.

-

Dando Valores a un Array

Podemos rellenar los arrays dando valor a cada uno de sus

elementos:

1

2

3

4

5

6

7

8

9

10

11

12

public class Persona {

private String nombre;

private int edad;

//... // Constructores, getters, setters

public static void main (String[] args){

Persona[] familia = new Persona[3];

familia[0] = new Persona("Maria", 35);

familia[1] = new Persona("Jose", 30);

familia[2] = new Persona("Ana", 3);

}

}

O bien utilizando un bucle:

1 public class Persona{

52

2

3

4

5

6

7

8

9

10

11

12

13

14

15

private String nombre;

private int edad;

//...// Constructores, getters, setters

public static void main (String[] args){

String[] nombres = {"Maria", "Jose", "Ana"};

int[] edades = {35, 30, 3};

Persona[] familia = new Persona[3];

for (int i = 0; i< familia.length; i++){

familia[i] = new Persona(nombres[i], edades[i]);

}

}

}

-

Asignaciones Legales en Arrays

De tipos primitivos:

Se puede asignar a una posición del array cualquier variable que

pueda ser promovidos o convertidos al tipo del array.

Por ejemplo, en un array de enteros podemos incluir shorts, bytes,

chars …

53

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

public class asignaArray{

public static void main(String[] args){

byte a = 3;

short b = 30000;

int c = 450;

long d = 300000L;

float e = 2500.203F;

double f = 3234.25;

char g = 'B';

int [] enteros = new int[10];

enteros[0] = a;

enteros[1] = b;

enteros[2] = c;

enteros[3] = (int) d;

enteros[4] = (int) e;v

enteros[5] = (int) f;

enteros[6] = g;

enteros[7] = c/a;

enteros[8] = (int) d/a;

enteros[9] = (int) e/a;</p>

for (int i = 0; i< enteros.length; i ++){

System.out.println("enteros["+ i + "]: " +

enteros[i]);

}

}

}

54

De tipos de referencia: Se puede asignar cualquier subclase del

tipo. Y si el tipo del array es una interfaz podemos asignar

cualquier objeto que implemente dicha interfaz.

Por ejemplo, podríamos tener un array de Empleado con distintos

tipos de empleado:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

//...

Empleado[] empleados = new Empleado[4];

empleados[0]=new Tecnico();

empleados[1]=new Secretario();

empleados[2]=new Contable();

empleados[3]=new Tecnico();

for (int i=0; i<empleados.length; i++){

Empleado e = empleados[i];

System.out.println(e.getNombre());

System.out.println(e.getAntiguedad());

System.out.println(e.getSueldo());

}

//...

Es incluso posible hacer una colección con objetos de cualquier

clase, ya que en Java todos los

objetos heredan de Object:

55

1

2

3

4

5

6

Object [] objetos = new Object[5];

objetos[0] = new Empleado();

objetos[1] = new Batman();

objetos[2] = new Libro();

objetos[3] = new String("Maria");

objetos[4] = new Bicicleta();

Asignaciones – Clases Envoltorio y Boxing

En Java los datos numéricos, de caracteres, lógicos se tratan de

forma primitiva por eficiencia.

Normalmente usamos tipos primitivos y no objetos.

Sin embargo, existe una manera de que estos datos puedan ser

objetos, usando las llamadas clases envoltorio. Cada tipo

primitivo en Java tiene su correspondiente clase envoltorio.

Son las siguientes:

Tipo primitivo Clase envoltorio

boolean Boolean

byte Byte

char Character

short Short

int Integer

long Long

float Float

double Double

Los objetos se construyen pasando el valor al constructor

correspondiente.

56

int entero = 500;

Integer Entero = new Integer(entero); //de primitivo a objeto se

llama boxing.

int aEntero = Entero.intValue(); //de objeto a

primitivo se llama unboxing.

Muchas veces es útil convertir a estas clases ya que tienen muchos

métodos interesantes.

String str = “1000”;

int x = Integer.parseInt(str);

En este último ejemplo hemos podido convertir de String a int

utilizando su clase envoltorio Integer y el

método parseInt.

Constructores:

Cada clase envoltorio (menos Character) tienen dos constructores:

uno que admite el tipo primitivo como parámetro y otro que admite

un String.

Integer a = new Integer(500);

Integer b = new Integer(“500”);

Float c = new Float(7.5f);

Float d = new Float(“7.5f”);

Character e = new Character(‘t’);

Para el constructor Boolean cuando el String es true (sin importar

mayúsculas o minúsculas) será true, cualquier otro valor será

falso.

Boolean f = new Boolean(false);

Boolean g = new Boolean(“TrUe”); //g será true.

Boolean h = new Boolean (“NO”); //h será false.

ValueOf:

Otra forma de construir un objeto de una clase envoltorio es

mediante este método estático, valueOf. Este método puede aceptar

un String, o un String y un parámetro que indique la base

numérica.

Integer nuevo = new Integer.valueOf(“150”);

Integer binario = new Integer.valueOf(“1010”, 2);

Métodos Útiles:

Convierte un Envoltorio en primitivo:

byteValue(), shortValue(), intValue(), longValue(),

floatValue(), doubleValue()

57

Convertir un String en primitivo:

parseByte(), parseShort(), parseInt(), parseLong(),

parseFloat(), parseDouble()

Dar una representación en String: toString()

Convertir números a otras bases. Con objetos Integer y Long

se pueden utilizar: toBinaryString(), toOctalString(),

toHexString():

Autoboxing y unboxing:

Para pasar de un tipo primitivo a su objeto equivalente se

necesita utilizar las clases envoltorio (boxing).

Para obtener de la referencia del objeto su tipo primitivo

(unboxing) se necesitan usar los métodos de las clases envoltorio.

Todas estas operaciones complicaban excesivamente el código. Por

ello a partir de J2SE 5.0 se introdujo una conversión automática

(autoboxing) que permite asignar y obtener los tipos primitivos

sin necesidad de utilizar las clases envoltorio.

Por ejemplo:

int enteroPrimitivo = 420;

Integer Entero = enteroPrimitivo; //Se permite la asignación

directa. Se llama autoboxing.

int otroEnteroPrimitivo = Entero; //Se asigna directamente. Se

llama autounboxing.

De modo que ahora el compilador según el caso se encarga de crear

el objeto envoltorio o de extraer el tipo primitivo.

También se permite en el paso de parámetros y en expresiones

aritméticas.

58

Asignaciones – Sobrecarga de Métodos con

Widening, Boxing y Clases Envoltorio

Vamos a ver algunos casos concretos relacionados con el Widening,

las Clases Envoltorio y los Argumentos-Variables que pueden

hacernos dudar en el momento de saber qué método elije el

compilador cuando tenemos métodos sobrecargados…

Sobrecarga y tipos primitivos:

Escogerá el menor tipo que sea mayor que el argumento en la

llamada.

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

class SobrecargaPrimitivos {

public static void main(String[] args){

byte byteVar = 10;

short shortVar = 10;

char charVar = 10;

long longVar = 10;

float floatVar = 10.0f;

System.out.println ("\nmetodo (byteVar)\u003B");

metodo (byteVar); //Llamada con un argumento byte

System.out.println ("\nmetodo (shortVar)\u003B");

metodo (shortVar); //Llamada con argumento short

System.out.println ("\nmetodo (charVar)\u003B");

metodo (charVar); //Llamada con argumento char

System.out.println ("\nmetodo (longVar)\u003B");

metodo (longVar); //Llamada con argumento long

59

22

23

24

25

26

27

28

29

30

31

System.out.println ("\nmetodo (floatVar)\u003B");

metodo (floatVar); //Llamada con argumento float

}

static void metodo (int v) { System.out.print ("-> amplia a int y ejecuta

metodo (int v) \n");}

static void metodo (long v) { System.out.print ("-> amplia a long y

ejecuta metodo (long v) \n");}

static void metodo (double v){System.out.print ("-> amplia a double y

ejecuta metodo (double v)\n");}

}

Sobrecarga Boxing y tipos primitivos:

Si el compilador tiene que elegir entre hacer un boxing (un método

con un parámetro Envoltorio) y un widening (método con un tipo

primitivo mayor) elegirá el widening.

1

2

3

4

class SobrecargaBoxing_Primitivos {

public static void main (String[] args){

int intVar = 10;

System.out.println ("\nmetodo (intVar)\u003B");

60

5

6

7

8

9

10

metodo (intVar); // A qué método llamará?

}

static void metodo (long v) { System.out.print ("-> amplia a long y ejecuta

metodo (long v)\n");}

static void metodo (Integer v) {System.out.print ("-> boxing a Integer y

ejecuta metodo (Integer v)\n");}

}

Sobrecarga Argumentos-Variables y tipos primitivos:

Si el compilador tiene que elegir entre un método con argumentos

variables y un widening (método con un tipo primitivo mayor)

elegirá el widening.

1

2

3

4

5

6

7

8

9

10

11

12

13

class SobrecargaVarArgs_Primitivos {

public static void main (String[] args){

int intVar = 10;

System.out.println ("\nmetodo (intVar)\u003B");

metodo (intVar);

}

static void metodo (long v) { System.out.print ( "-> amplia a long y

ejecuta metodo (long v) \n");}

61

14

15

static void metodo (int ... v) {System.out.print ( "-> ejecuta metodo

(int.. v) \n");}

}

Sobrecarga Boxing y Argumentos-Variables:

Cuando el compilador tiene que elegir entre hacer boxing y un

método con argumentos variables, se queda con el boxing.

1

2

3

4

5

6

7

8

9

class SobrecargaBoxing_VarArgs {

public static void main (String[] args){

int intVar = 10;

System.out.println ("\nmetodo (intVar)\u003B");

metodo (intVar);

}

static void metodo (Integer v) { System.out.print ( "-> boxing a Integer y

ejecuta metodo (Integer v) \n");}

static void metodo (int ... v) {System.out.print ( "-> ejecuta metodo (int..

v) \n");}

}

Sobrecarga y Tipos de Referencia:

62

El compilador hace widening si encuentra un objeto de una subclase

y el método tiene como parámetro un objeto de la superclase. Esto

es debido a que un objeto hijo se puede utilizar donde se espera

un objeto padre y no al contrario.

Sobrecarga con widening y boxing a la vez:

Veamos qué ocurre en los casos en los que el compilador necesita

hacer estas dos conversiones para llamar al método.

Amplía y luego Boxing: en este caso el compilador no puede

hacer esta operación. Da error de compilación.

1

2

3

4

5

6

7

8

9

10

public class wideAndBox{

public static void main(String[] args){

byte byteVar = 5;

System.out.println ("\nmetodo (byteVar)\u003B");

metodo(byteVar);

}

static void metodo(Long v){

System.out.println("-> ejecuta metodo (Long v) \n");

}

}

El compilador tampoco puede hacer las operaciones cambiando el

orden, primero Boxing y luego ampliando porque un Byte no se

puede ampliar a Long (Un Byte no es un Long, no están relacionados

por herencia).

Boxing y luego amplía: el compilador sí puede hacer estas dos

conversiones si despúes de hacer Boxing se puede ampliar

porque estén relacionados por herencia. Un Byte es un Object.

1 public class BoxAndWide{

63

2

3

4

5

6

7

8

9

10

public static void main(String[] args){

byte byteVar = 5;

System.out.println ("\nmetodo (byteVar)\u003B");

metodo(byteVar);

}

static void metodo(Object v){

System.out.println("-> ejecuta metodo (Object v)

\n");

}

}

Combinando Boxing y Widening con VarArgs:

El compilador no tiene problema cuando un método tiene argumentos

variables ya haga falta hacer widening o boxing.

1

2

3

4

5

6

7

8

9

10

public class ejem{

public static void main(String[] args){

int entero = 10;

metodo1 (entero, entero);

metodo2 (entero, entero);

}

static void metodo1(long... v) {

System.out.println("metodo(Long... )");}

64

11 static void metodo2(Integer... v)

{System.out.println("metodo(Integer... )");}

}

Resumen:

Entre diferentes métodos con tipos primitivos, el compilador

escoge el menor tipo que sea mayor al argumento de llamada.

En los tres siguientes casos gana la característica más antigua en

Java:

Entre Boxing y Widening, gana Widening.

Entre Args Variables y Widening, gana Widening.

Entre Boxing y Argumentos Variables, gana Boxing.

Argumentos Variables con Boxing o Argumentos Variables con

widening, ambos funcionan.

Widening y Boxing, no funciona (int pasaría a long, pero long

no puede convertirse en Long).

Boxing y Widening, funciona (int pasaría a Integer e Integer

puede convertirse en Object).

El recolector de basura

Muchos lenguajes de programación permiten asignar memoria dinámica

en tiempo de ejecución pero es el programador el que tiene la

responsabilidad de liberarla.

65

Sin embargo, Java proporciona un hilo en el nivel del sistema que

administra esta memoria, busca la que está en desuso y la libera.

Es el recolector de basura o Garbage Collector (GC).

Cuándo se ejecuta:

El GC está bajo el control de la JVM y ella decide cuándo

ejecutarlo. Dentro de un programa se puede solicitar la ejecución

pero no hay ninguna garantía de que se ejecute.

Cómo funciona:

Los modelos de reciclaje de la memoria pueden variar

considerablemente en cada implementación de la JVM. Lo que debemos

tener claro es cuándo un objeto es elegible por el GC.

Un objeto es elegible cuando ningún thread vivo puede acceder a

él.

Cómo hacer un objeto elegible:

Estableciendo la referencia a null:

Cuando asignamos la referencia de un objeto a null estamos

perdiendo cualquier posibilidad de acceder a él.

1

2

3

4

5

6

7

public class aNull{

public static void main (String[] args){

Integer a = new Integer(10); //1

a = null; //2

//ahora el objeto Integer(10) es elegible por el GC.

}

}

//1 Creación del objeto.

//2 La referencia ya no apunta al objeto. La referencia es

igual a null. El objeto es ahora elegible por el GC.

66

Reasignando la referencia:

Cuando hacemos que la referencia que apunta a un objeto

apunte a otro distinto dejando el primero inaccesible.

1

2

3

4

5

6

7

8

public class Reasigno{

public static void main (String[] args){

Integer a = new Integer(10); //1

Integer b = new Integer(20); //2

a = b; //3

//ahora el objeto Integer(10) es elegible por el

GC.

}

}

//1 y //2 Creación de los objetos.

//3 La referencia a y b están ahora apuntando al segundo

objeto, siendo el primero elegible para el GC.

Cuando la referencia está aíslada:

Se produce cuando un objeto tiene como miembros otros objetos

de su misma clase y aunque se pierdan las referencias de

estos objetos aún conservan entre ellos referencias vivas que

son elegibles para el GC.

1 public class Aislada{

67

2

3

4

5

6

7

8

9

10

11

12

Aislada vecina;

public static void main (String[] args){

Aislada a = new Aislada(); //1

Aislada b = new Aislada();

a.vecina = b; //2

b.vecina = c;

a = null; //3

b = null; // Aunque las pongamos a null entre los

objetos tienen referencias.

}

}

//1 y //2: Creación de los objetos Aislada y asignación de la

vecina.

//3: Se ponen las referencias a null. Los dos objetos

Aisladas son elegibles para el GC.

Solicitar la ejecución del GC:

Como hemos dicho antes no se puede forzar la ejecución del

recolector de basuras. Se solicita su ejecución mediante la

siguiente instrucción pero no se nos garantiza que se ejecute.

Se puede ejecutar mediante:

System.gc();

U obteniendo una instancia de Runtime:

68

Runtime rt = Runtime.getRuntime();

rt.gc();

Método finalize():

Este método está definido en la clase Object y por tanto todos los

objetos lo heredan. En él

podemos escribir cualquier código que queramos que se ejecute

antes de que el GC borre el objeto.

Como la ejecución del GC no está garantizada que se ejecute la

ejecución de finalize() tampoco está garantizada.

Cada objeto sólo podrá llamar a este método una vez. En este

código se podría hacer inelegible el objeto, por ejemplo,

volviendo a asignarle una referencia.

Los Operadores en Java (I)

Operadores

Los operadores son símbolos especiales que producen un nuevo valor

a partir de uno o varios operandos.

Los valores producidos son, en la mayoría de los casos, booleanos

o numéricos.

En la imagen tendríamos un ejemplo de un operador binario, que a

partir de dos operandos produciría un nuevo valor.

En la siguiente tabla mostramos los operadores en Java, su orden

de precedencia y su asociatividad.

(I-D: de izquierda a derecha, D-I: de derecha a izquierda).

69

Operador de Asignación: =

Cuando asignamos valor a variables de tipo primitivo hay casos en

los que se produce un casting implícito, otros en los que es

necesario un casting explícito y casos en los que se puede

producir un truncamiento.

Cuando asignamos valores a variables de tipos de referencia

debemos tener cuidado con el tipo.

Esto lo podemos ver en los posts del Tema3.

Operadores de Asignación Compuestos: +=, -=, *=, /=

Se tratan en el examen los cuatro anteriores por ser los más

usuales.

Algunos ejemplos:

1

2

3

4

x += 10; //sería igual a x = x + 10;

y -= 50; //sería igual a y = y - 50;

z *= 7; // equivale a z = z * 7;

w /= 100; // equivale a w = w/100;

En los operadores compuestos la expresión de la derecha siempre se

evalúa primero.

70

1 x *= 3 + 5; //sería equilvalente a x *= (3+5), es decir x = x * (3 +

5) y no x = (x * 3) + 5.

Operadores Relacionales: <, <=, >, >=

Siempre devuelven true or false. Se suelen utilizar en sentencias

ifs para comprobar condiciones o en expresiones para asignar el

valor resultante a una variable boolean.

Pueden comparar cualquier combinación de enteros, reales o

carácteres. Cuando se compara con un carácter se utiliza su valor

Unicode numérico.

Ejemplos:

1

2

3

4

5

6

7

8

9

10

boolean resultado;

int x = 200;

if (x<=100)

{

resultado = true;

}

else

{

resultado = false;

}

También se podría hacer directamente:

1

2

boolean resultado;

resultado = (x<=100);

Con caracteres:

1

2

3

char a = 'A';

char b = 'B';

boolean resultado = a>b;

Operadores de Igualdad: ==, !=

71

Se pueden comparar dos números, dos carácteres, dos boolean o dos

variables de referencia.

Cuando se comparan tipos primitivos devolverá true si ambos

valores son iguales.

Cuando se comparan referencias devolverá true si las variables

apuntan al mismo objeto.

(No si dos objetos son iguales).

Ejemplos:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

class Igualdad{

public static void main (String[] args)

{

int a = 100;

char b = 'b';

boolean res = true;

char[] letras1 = {'a','b','c','d'};

char[] letras2 = {'a','b','c','d'};

if (a == 100L)

{ System.out.println("100 y 100L, Iguales");}

if (res != (b&gt;a))

{ System.out.println("res distinto a (b>;a)");}

if (letras1 == letras2) //Imprime distinto aunque ambos

tengan el mismo contenido.

{ System.out.println("Iguales");} //porque cada referencia

apunta a un obj distinto.

else

{ System.out.println("Distintos");}

letras2 = letras1; //hacemos que letras2 apunte a letras1

72

25

26

27

28

29

if (letras1 == letras2) //Imprimirá iguales porque ahora

{ System.out.println("Iguales");} //apuntan al mismo

objeto.

else

{ System.out.println("Distintos");}

}

}

Operador instanceof:

Se utiliza solo con referencias y comprueba si un objeto es de una

clase o interfaz determinada.

Por ejemplo, supongamos la siguiente jerarquía de clases:

La clase Persona tiene una subclase Empleado. Empleado a su vez

tiene una subclase Tecnico.

Además la clase Persona implementa la interfaz Modales.

Con las siguientes declaraciones:

1

2

3

4

5

Persona p1 = new Persona();

Persona p2 = new Empleado();

Empleado e1 = new Empleado();

Tecnico t1 = new Tecnico();

Boolean b;

¿Qué devolvería el operador instanceof en las siguientes líneas de

código?

73

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

b = p1 is instanceof Persona;

b = p1 is instanceof Modales;

b = p1 is instanceof Object;

b = p1 is instanceof Empleado;

b = p1 is instanceof Tecnico;

b = p2 is instanceof Persona;

b = p2 is instanceof Empleado;

b = p2 is instanceof Modales;

b = p2 is instanceof Tecnico;

b = e1 is instanceof Empleado;

b = e1 is instanceof Modales;

b = e1 is instanceof Persona;

b = e1 is instanceof Tecnico;

b = t1 is instanceof Persona;

b = t1 is instanceof Modales;

b = t1 is instanceof Empleado;

b = t1 is instanceof Tecnico;

b = null is instanceof Persona;

b = e1 is instanceof String;

Seguimos con el resto de operadores en la siguiente entrada…

Los Operadores en Java (II)

Operador Resto: %

Devuelve el resto de una división.

74

1

2

3

4

5

6

7

8

9

10

11

class Resto{

public static void main (String[] args){

int dividendo = 15;

int divisor = 2;

int resto = dividendo % divisor;

System.out.println("Resto: " + resto);

int expresion = 3 + dividendo % divisor;

System.out.println("Expresion: " + expresion);

}

}

Hay que recordar que los operadores *, /, % tienen una prioridad

mayor que los operadores + y -, como se puede comprobar en el

ejemplo.

Recordamos también que las expresiones se evalúan por defecto de

izquierda a derecha y que para cambiar la prioridad en las

evaluaciones se pueden utilizar paréntesis.

Operador Concatenador: +

Con este operador se realiza una concatenación de objetos String y

resulta en un nuevo objeto String.

1

2

3

4

String sujeto = "Ellos";

String predicado = "estudiaban Java";

String frase = sujeto + " " + predicado;

System.out.println(frase); //Devuelve "

Si uno de los argumentos del operador + es String el otro

argumento se convierte en objeto String.

Por ejemplo, entre números y String:

1

2

3

class PruebaConcatenador{

public static void main (String[] args){

String nombre = "Virginia";

75

4

5

6

7

8

9

10

11

int a = 10;

int b = 20;

System.out.println(nombre + a + b);

System.out.println(a + b + nombre);

System.out.println(a + b + nombre + a + b);

}

}

Devolvería:

Todos los objetos pueden convertirse en objeto String de forma

automática, pero el resultado puede tener poco significado. Es por

esto que para dar una representación significativa de un objeto en

String se suele sobrecargar el método toString().

Operadores de Incremento y Decremento: ++, –

Estos operadores se pueden colocar delante de la variable

(notación prefjia) o después de la variable (notación sufija) y

realizan un incremento o decremento en 1 en el valor de la

variable.

Cuando tenemos la notación prefija (++i ó –i), primero se realiza

el aumento o decremento en el valor y luego se evalúa.

Cuando tenemos la notaciión sufija (i++ ó i–), primero se evalúa y

luego se realiza el aumento o decremento en el valor.

Por ejemplo:

1

2

3

4

5

class OperadoresIncrDecr{

public static void main (String[] args){

int a = 10;

int b = 10;

76

6

7

8

9

10

11

System.out.println("a++: " + a++); //1º Evalua con a y luego

la incrementa.

System.out.println("++b: " + ++b); //1º Incrementa y luego

evalua con el nuevo valor.

System.out.println(a); //Comprobamos que el valor se ha

incrementado.

System.out.println(b); //Comprobamos que el valor se ha

incrementado.

}

}

Devuelve:

Operador Condicional: ?:

El siguiente operador evalúa una condición y asigna un valor si es

verdadera u otro valor si es falsa.

x = (expresion logica) ? valorSiVerdadero : valorSiFalso

Por ejemplo:

1

2

int nota = 80;

String resultado = (nota >= 65)? "Aprobado!!!" : "Suspenso";

Operadores Lógicos: &, |, &&, ||, ^, !

& y | son los operadores And y Or lógicos. Evalúan siempre ambas

condiciones. No tienen evaluación perezosa.

&& y || son los operadores And y Or lógicos con evaluación

perezosa.

Quiere decir que si pueden dar el resultado evaluando solo la

primera condición no evaluarán la segunda.

Esto se da cuando:

77

La primera condición en || es cierta, independientemente de

la segunda condición el resultado ya es cierto.

La primera condición en && es falsa, independientemente de la

segunda condición el resultado es falso.

Un ejemplo con operadores de incremento para comprobar los

diferentes resultados que podemos obtener si usamos un operador

con evaluación normal o perezosa:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

class Logicos{

public static void main (String[] args){

int a = 0; //Valores para operadores &, |

int b = 5;

int c = 0; //Valores para operadores perezosos &&, ||

int d = 5;

if ( a == 0 | b++ > 2) //Evalúa las dos condiciones y b se

incrementa.

{ System.out.println("Cierto (a==0 | b++ > 2)-> a: " + a + "

b: " + b); }

if (c == 0 || d++ > 2) //Evalúa solo la 1ª condición al ser

cierta (c==0), b no incrementa.

{ System.out.println("Cierto (c==0 || d++ > 2)-> c: " + c + "

d: " + d); }

System.out.println();

if (a > 0 & b++ < 5) //Evalúa las dos condiciones, b se

incrementa de nuevo.

{ }

else

{ System.out.println("Falso (a>0 & b++ a: " + a + " b: " +

b); }

78

25

26

if (c > 0 && d++ < 5) //Evalúa solo la 1ª condición por ser

falsa (c > 0), d no incrementa.

{ }

else

{ System.out.println("Falso (c>0 && d++ <5) -> c: " + c + "

d: " + d); }

}

}

Devuelve:

^ es el operador Xor. Devueve cierto cuando una de las condiciones

es falsa y la otra verdadera.

Siempre evalúa ambos operandos.

! es el operador Not. Si la condición es cierta devuelve falso y

si es falsa devuelve cierto.

Con esto terminamos el tema de Operadores.

Recordaros que los operadores de desplazamiento de bits no entran

en el examen para la versión 6. Os podéis encontrar preguntas

sobre desplazamiento de bits en cuestionarios por ser de versiones

anteriores.

En este post tenéis algunos ejercicios resueltos sobre Operadores.

Control del Flujo: If y Switch

Las sentencias condicionales permiten ejecutar código de forma

selectiva.

En Java tenemos las sentencias condicionales if y switch.

Sentencia If:

La sintaxis más sencilla de una sentencia if es:

1

2

if (expresionLogica) //Si expresionLogica se evalúa a true

entonces se ejecuta codigo.

codigo

79

Si se tiene una sola sentencia de código no es obligatorio

encerrarla entre llaves pero siempre es recomendable por

legibilidad.

Ejemplo:

1

2

3

if (precio < 100){

comprar(); //Mas legible entre llaves.

}

Si nos encontramos un if sin llaves debemos recordar que solo

aplica a la primera sentencia.

1

2

3

4

if (precio < 100)

comprar(); //Solo esta sentencia forma parte del if.

salirTienda(); //No forma parte del código del if.

Siempre se ejecuta.

volverACasa(); //No forma parte del código del if.

Siempre se ejecuta.

La cláusula else es opcional. Se incluye cuando queremos que se

ejecute código en el caso de que no se cumpla la condición en el

if.

1

2

3

4

5

6

if (precio < 100){

comprar();

}

else{

System.out.println("Yo no soy tonto.");

}

También podemos tener una estructura if, else-if, else. Se da en

los casos en los que queremos comprobar varias condiciones.

1

2

3

4

if (precio < 100){

comprar();

}

else if (price < 2000 && tengoCredito){

80

5

6

7

8

9

comprarConTarjeta();

}

else{

System.out.println("Yo no soy tonto.");

}

En las sentencias If se cumplirá:

Habrá 0 o muchos else-if y precederán a la cláusula else.

Habrá 0 o 1 else e irá a continuación de todas las cláusulas

else-if.

Cuando una condición se cumple el resto no se comprueba.

Si tenemos un else y no sabemos a que if corresponde será el

más cercano sin else.

La asignación en una condición no da error si se trata de un

lógico: if(indicador=true)

Switch

Con esta sentencia vamos a evaluar una expresión y dependiendo de

su valor ejecutaremos un código u otro:

1

2

3

4

5

6

switch (expresion){

case constante1: codigo1

case constante2: codigo2

case constante3: codigo3

default: codigo

}

Si expresion coincide con alguna de las constantes se ejecutará el

código de ese case y se seguirá ejecutando el código de los

siguientes case hacia abajo hasta que se encuentre una sentencia

break.

La sentencia default es opcional y se puede especificar cuando

queremos que se ejecute un código para cualquier valor que sea

distinto a los especificados en los case.

En los siguientes ejemplos podemos comprobar cómo afecta al

resultado la existencia y posición de las sentencias breaks y

default:

81

1. Ejemplo de un Switch Típico.

Switch con breaks en cada case y una clásula default al final.

Recorremos el array niveles. Cada valor entra en su case

correspondiente y para al tener la sentencia break. Al estar la

cláusula default al final no hace falta que tenga break.

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

public class EjemploSwitch1{

public static void main (String[] args){

int[] niveles = {1, 2, 3, 0};

System.out.println("\nEjecucion Switch Tipico (breaks y

default al final): " );

for (int i=0; i < niveles.length; i++){ {

System.out.println("Nivel: " + niveles[i]);

switch (niveles[i]){

case 1: System.out.println("\tNivel Basico.");

break;

case 2: System.out.println("\tNivel

Intermedio.");

break;

case 3: System.out.println("\tNivel Avanzado.");

break;

default: System.out.println("\tNivel no

informado.");

}

}

}

}

Resultado:

82

2. Sentencia Default No al Final.

En este caso hemos colocado la sentencia default sin break después

del primer case. Todos los case tienen break. Cuando niveles[i] es

igual a 0, ejecuta el código asociado a default. Al no tener break

pasa al case 2, ejecuta su código y entonces sí para al tener

break.

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

public class EjemploSwitch2{

public static void main (String[] args){

int[] niveles = {1, 2, 3, 0};

System.out.println("\nEjecucion Switch con Breaks Default

enmedio sin break: " );

for (int i=0; i < niveles.length; i++) {

System.out.println("Nivel: " + niveles[i]);

switch (niveles[i]){

case 1: System.out.println("\tNivel Basico.");

break;

default: System.out.println("\tNivel no

informado.");

case 2: System.out.println("\tNivel

Intermedio.");

break;

case 3: System.out.println("\tNivel

Avanzado.");

break;

83

21

22

}

}

}

}

Ejecución:

3. Sentencias Case sin Break (Ejemplo en el que no tiene lógica

aplicarlo).

En este Switch comprobamos qué ocurre si no colocamos ningún

break. Para cada valor de niveles se ejecutan todos los case y

default.

Ejecución:

4. Sentencias Case Sin Break (Ejemplo en el que sí podría ser

útil).

En este caso comprobamos cómo a veces puede tener lógica para

nuestro código dejar los case sin break. Hemos cambiado el orden

de las constantes en los case.

1

2

3

4

public class EjemploSwitch4{

public static void main (String[] args){

int[] niveles = {1, 2, 3, 0};

84

5

6

7

8

9

10

11

12

13

System.out.println("\nEjecucion Switch sin Breaks (con logica que

podria ser correcta): ");

for (int i=0; i < niveles.length; i++)

{

System.out.println("Nivel: " + niveles[i]);

switch (niveles[i])

{

case 3: System.out.println("\tNivel Avanzado.");

case 2: System.out.println("\tNivel Intermedio.");

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

public class EjemploSwitch3{

public static void main (String[] args){

int[] niveles = {1, 2, 3, 0};

System.out.println("\nEjecucion Switch sin Breaks (entra en

todos los case cada vez): ");

for (int i=0; i < niveles.length; i++)

{

System.out.println("Nivel: " + niveles[i]);

switch (niveles[i])

{

case 1: System.out.println("\tNivel Basico.");

case 2: System.out.println("\tNivel Intermedio.");

case 3: System.out.println("\tNivel Avanzado.");

default: System.out.println("\tNivel no informado.");

}

}

}

}

85

14

15

16

17

18

19

20

21

22

case 1: System.out.println("\tNivel Basico.");

break;

default: System.out.println("\tNivel no informado.");

}

}

}

}

Ejecución:

En el switch se debe cumplir:

La expresión evaluará a char, byte, short, int o enum.

Las constantes deben ser constantes en tiempo de compilación.

Hay que usar break cuando se necesite parar evitar la

ejecución de los case posteriores.

La cláusula default no tiene por qué ser la última.

No puede haber dos case comprobando el mismo valor. No

compilará.

No se pueden omitir las cláusulas case ni los ‘:’

Control del Flujo: Bucles

Los bucles nos permiten alterar el flujo de ejecución de un

programa ejecutando repetidamente una sentecia o grupo de

sentencias.

86

En Java 6 tenemos tres opciones distintas a la hora de realizar

iteraciones:

while,

do while,

for (normal y mejorado)

Vamos a ver cada una de ellas en detalle.

While

Es el bucle indicado cuando no se sabe de antemano cuántas veces

se va a ejecutar el bucle. Simplemente queremos que se repitan

ciertas acciones mientras la condición del bucle sea cierta.

1

2

3

4

//inicialización de variables

while (condicion){ //La condicion debe evaluarse a boolean

//codigo //Puede no ejecutarse ninguna vez

}

Cómo funciona:

Al entrar en el while se comprueba

la condicion por primera vez.

Si resulta falsa no llega a

ejecutar el código y se prosigue

la ejecución fuera del bucle.

Si es cierta se ejecuta el código

y se vuelve a comprobar la

condición.

Así sucesivamente hasta que la

condición devuelva falso y

salgamos del bucle.

Como veremos la última sentencia

que se ejecutará en un bucle

siempre será la comprobación de la

condición.

Por ejemplo:

En este código la variable i

vuelve a incrementarse en la última comprobación de la condición

del bucle, cuando ésta ya es falsa.

87

1

2

3

4

5

6

7

8

9

10

11

12

13

public class EjemWhile1{

public static void main (String[] args){

int i = -1;

boolean encontrado = false;

int[] medidas = {10, 30, 40, 50, 70};

while (++i < 5 && !encontrado){

if(medidas[i] == 40){

encontrado = true;

}

}

System.out.println(i);

}

}

En los bucles while estar atento a:

Declaración de variables en la condición del while. No se

puede, deben estar declaradas con anterioridad.

Expresiones en la condición del while que no sean boolean.

Por ejemplo: enteros en lugar de boolean o asignaciones en

lugar de comparaciones, darán error de compilación.

Do While

En estos bucles primero se ejecuta el código y luego se comprueba

la condición, por lo que tenemos asegurado que el código se

ejecuta al menos una vez.

1

2

3

4

do{

elem = pila.obtener();

System.out.println("elem: " + elem);

} while (!pila.esVacia());

88

Cómo funciona:

En primer lugar se ejecuta el código del bucle.

A continuación se comprueba la condición. Si esta es falsa se

prosigue la ejecución fuera del bucle. Si es verdadera se ejecuta

el código y se vuelve a comprobar la condición.

Estar atento a las mismas consideraciones que en el while.

For

Es el bucle que solemos utilizar cuando sabemos cuántas veces

queremos ejecutar cierto código.

En su sintaxis podemos destacar tres partes importantes:

- declaración e inicialización de variables

- condición

- incremento.

Aunque ninguna de ellas son obligatorias.

Ejemplo de bucle mínimo for:

1

2

3

for ( ; ; ){ //sin inicializacion, condición ni incremento.

Sería un bucle infinito.

//infinito

89

}

Ejemplo de un bucle for que equivaldría a un while:(sin

inicialización ni incremento)

1

2

3

4

int i = 0;

for ( ; i<10 ; ){

i++;

}

Ejemplo de un bucle for típico:

1

2

3

4

5

6

for (int i=0;i<10;i++){

//declaración e inicializacion "int i=0"

//condicion "i<10"

//incremento "i++"

System.out.println("i: " + i);

}

Cómo funciona:

90

La inicialización de la variable se ejecuta al principio una sola

vez.

A continuación se comprueba la condición y si es falsa se prosigue

la ejecución fuera del bucle.

Si resulta cierta se ejecuta

el código, se incrementa la

variable y se vuelve a

comprobar la condición.

Consideraciones del For.

Declaración e inicialización:

Se pueden declarar 0, 1

o más variables del

mismo tipo. Si hay más

de una se separan por

comas.

Las variables declaradas

en el for pierden su

ámbito al salir del

bucle for.

Esta parte sólo se

ejecuta una vez, al principio de la ejecución del bucle.

Condición:

Sólo se puede comprobar una condición no varias.

Control del Flujo: Bucles II (break, continue, etiquetas)

Una vez que hemos visto los bucles en Java vamos a ver cómo les

afectan las siguientes sentencias: break y continue.

Escribimos un bucle anidado y vemos la diferencia entre break y

continue:

1

2

3

4

5

public class BucleConBreak{

public static void main(String[] args){

for (int i = 0; i< 6; i++){

for (int j=0; j<5; j++){

if (j==3){

91

6

7

8

9

10

11

12

13

14

break;

}

else{

System.out.println("iteracion [i,j]: " +

i + ", " + j);

}

}

}

}

}

El resultado es:

La sentencia break hace que se salga completamente del bucle más

interno y proseguimos con la siguiente iteración del bucle

externo.

El mismo código pero con continue:

1

2

3

4

5

6

public class BucleConContinue{

public static void main(String[] args){

for (int i = 0; i< 6; i++){

for (int j=0; j<5; j++){

if (j==3){

continue;

92

7

8

9

10

11

12

13

14

}

else{

System.out.println("iteracion [i,j]: "

+ i + ", " + j);

}

}

}

}

}

Devuelve:

La sentencia continue hace que pasemos a la siguiente iteración

del bucle más interno.

Etiquetas

Es común utilizar las etiquetas con bucles for o while y en

conjunción con las sentencias break y continue.

Una etiqueta se coloca antes de la sentencia a etiquetar seguida

de ‘:’

Son útiles cuando tenemos bucles anidados y queremos especificar

en cuál de ellos queremos hacer un break o continue.

93

En el siguiente ejemplo tenemos un ejemplo de break etiquetado:

El funcionamiento normal del break sería salir del bucle más

interno, el while en este caso.

Sin embargo, el break etiquetado hace que rompa el bucle

etiquetado, es decir el for.

1

2

3

4

5

6

7

8

9

10

11

12

13

14

public class BucleBreakLabeled{

public static void main (String[] args){

boolean esVerdadero = true;

externo: //etiqueta la siguiente sentencia, es decir el for.

for (int i = 0; i<5; i++){

while (esVerdadero){

System.out.println("Hola!");

break externo; //break con etiqueta, hace que rompa la

sentencia de la etiqueta, el for.

}

System.out.println("Despues del while!");

}

System.out.println("Despues del for!");

}

}

Devuelve:

Las sentencias break y continue con etiquetas deben estar dentro

de un bucle con la misma etiqueta, si no dará error de

compilación.

Control del Flujo: Excepciones

Excepción

Es un evento excepcional que altera el flujo normal del programa.

Cuando esto ocurre decimos que se ha lanzado una excepción.

Una excepción se puede generar:

en la JVM, como respuesta a una condición inesperada (Caso

1).

94

en el código, lanzadas explícitamente por el programador

utilizando la sentencia throw (Caso 2).

Las excepciones del primer caso se llaman no comprobadas

(unchecked) y las del segundo comprobadas (checked).

Ejemplo Caso 1: No comprobadas.

Por ejemplo, al intentar acceder a una posición inválida en un

array.

1

2

3

4

5

6

7

8

9

public class PruebaOutOfBounds {

public static void main (String[] args){

int[] notas = new int[5];

for (int i = 0; i<=5; i++){

notas[i] = i*2;

System.out.println("notas["+i+"]=" + notas[i]);

}

}

}

Otras excepciones no comprobadas son: ArithmeticException,

ClassCastException, NullPointerException, …

En general son aquellas que descienden de la

clase RuntimeException.

Ejemplo Caso 2: Comprobadas.

Cuando lanzamos explícitamente una excepción.

1

2

3

4

5

...

public void reverse() throws Exception{

if (nombre == null){

throw new Exception ("El nombre es nulo.\n Lanzamos

excepcion al intentar hacer

95

6

7

8

9

10

11

12

13

reverse().");

}

else

{

for (int i = nombre.length()-1; i >=0; i --)

System.out.println(nombre.charAt(i));

}

}

...

En general son aquellas que descienden de la clase Exception,

excluyendo RuntimeException.

Regla del Manejo o la Declaración

Las excepciones comprobadas que un método pueda lanzar se deben

declarar.

Si se produce una excepción comprobada en el código, el método

debe:

manejar la excepción utilizando el bloque try-catch-finally

ó declarar la excepción y pasar la responsabilidad de

declaración o gestión al método llamante.

Para declarar la excepción se utiliza la sentencia throws.

Manejando Excepciones

El manejo de excepciones en Java se lleva a cabo principalmente

mediante las sentencias: try, catch y finally.

1

2

try{

.... //Código que puede provocar una excepcion

96

3

4

5

6

7

8

9

10

}

catch (Excepcion1){ //Puede haber 0, 1 o muchos bloques

catch.

//En caso de haber 0 debe existir bloque finally.

....

}

finally{ //Puede haber 0 o 1 bloque finally.

.... //En caso de haber 0 debe existir bloque catch.

}

En el bloque try encerramos el código que puede lanzar una

excepción.

En el bloque catch definimos qué excepción y con qué código

tratarla.

En el bloque finally escribimos código que siempre se ejecuta se

produzca o no la excepción.

Ejemplo:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

public class Alumno{

//...

public static void main (String[] args){

try{

Alumno a = new Alumno("Ana", 12, 6);

System.out.println(a);

a.reverse();

Alumno c = new Alumno();

System.out.println(c);

c.reverse();

}

catch (Exception e) {

97

16

17

18

19

20

21

System.out.println("--> Hemos capturado la

excepcion!!.");

}

finally {

System.out.println("--> Siempre se ejecuta se

produzca o no excepcion.");

}

}

}

Restricciones de los bloques try-catch-finally:

Un bloque try debe tener un bloque catch o un bloque finally

asociado.

Entre un bloque try y catch no puede escribirse código.

Si hay varios bloques catch las excepciones más específicas

deben colocarse antes que las más generales.

Assertions: Introducción y Ejemplo

Una aserción es una expresión lógica que el programador supone que

siempre será verdadera cuando se ejecute. Es una forma de

confirmar que esta suposición es cierta durante el desarrollo y

así evitar tener que programar código que compruebe o controle

casos que no se producirán.

Tenemos dos formas de utilizar las assertions:

assert(expresion_logica);

assert(expresion_logica): expresion_valor;

La segunda sintaxis nos permite además establecer una expresión

que nos dará más información sobre el error.

Esta expresion_valor será cualquier expresión que pueda ser

convertida a String, es decir puede ser un valor primitivo, una

cadena, un objeto, una función que devuelva alguna de las

anteriores, etcétera.

Características:

98

Se incorporaron en la versión J2SE 1.4

Siempre se afirma que algo será verdadero.

Las assertions están deshabilitadas por defecto.

Para habilitar las assertions añadir al comando java el

parámetro -ea (ó -enable assertions).

Para deshabilitar las assertions añadir al comando java el

parámetro -da (ó -disableassertions).

La clase java.lang.AssertionError desciende de

java.lang.Error y ésta a su vez de java.lang.Throwable.

Ejemplo:

En este programa vamos a construir una clase Triangulo. Tendremos

un constructor con los lados como parámetros. En la función

esRectangulo que devuelve true si el triángulo es rectángulo y

false en caso contrario, colocamos una aserción sobre los lados

del triángulo.

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

import java.lang.Math;

public class Triangulo {

private float ladoa;

private float ladob;

private float ladoc;

public Triangulo(float ladoa, float ladob, float ladoc){

this.ladoa = ladoa;

this.ladob = ladob;

this.ladoc = ladoc;

}

public boolean esRectangulo(){

//Sabemos un lado de un triangulo debe ser menor que

la suma

//de los otros dos lados.

99

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

assert(ladoa < ladob + ladoc): "\nladoa: " + ladoa

+ " no es < que: "

+ ladob + " + " +

ladoc;

return(Math.pow(ladoc,2) == Math.pow(ladoa,2) +

Math.pow(ladob,2));

}

public static void main (String[] args){

//Triangulo con valores de lados correctos.

Triangulo t1 = new Triangulo(6,8,10)

If t1.esRectangulo(){

System.out.println("El triangulo t1 es

rectangulo");

}

//Creamos un triangulo cuyos lados harán saltar la

assertion.

Triangulo t2 = new Triangulo(10,3,2);

if t2.esRectangulo(){

System.out.println("El triangulo t2 es

rectangulo");

}

}

}

Resultado:

Vemos en la captura que primero hemos compilado el fichero fuente.

En segundo lugar, hemos ejecutado el programa, pero al estar

deshabilitadas las assertion por defecto no ocurre ningún error.

Por último ejecutamos el programa habilitando las assertions y

vemos que la condición no se cumple al comprobar los lados del

segundo triángulo.

Nos añade a la traza del error la cadena “ladoa: 10.0 no es < que:

3.0 + 2.0″

100

Las clases de cadena: String, StringBuffer y StringBuilder

Java proporciona tres clases diferentes para tratar con cadenas de

caracteres:

java.lang.String

java.lang.StringBuffer

java.lang.StringBuilder

Los objetos String son cadenas de caracteres estáticas, al crearse

e inicializarse no pueden cambiar su valor. Es porque son

inmutables que pueden ser compartidas por varias referencias.

Los objetos StringBuffer y StringBuilder, por el contrario, son

cadenas de caracteres dinámicas, pueden cambiar su contenido y su

capacidad.

La diferencia entre los objetos StringBuffer y StringBuilder es

que los primeros pueden ser utilizados de forma segura por varios

hilos, siendo sus métodos sincronizados mientras que los objetos

StringBuilder no.

La clase StringBuilder se añadió en la versión J2SE 1.5.

Inmutabilidad en la clase String

Con el siguiente ejemplo, puede parecer que el String saludo sí

cambia, pero lo que en realidad sucede es que la referencia se

asigna a una nueva cadena creada:

1 public class Saludo{

101

2

3

4

5

6

7

public static void main (String[] args){

String saludo = "Hola ";

saludo = saludo + "Rosa";

System.out.println(saludo);

}

}

Si no se reasigna la referencia no estamos apuntando a una nueva

cadena. Las cadenas que se crean se quedan sin referencia alguna.

Por ejemplo:

1

2

3

4

5

6

7

8

public class Saludo2{

public static void main(String[] args){

String saludo = "Hola ";

saludo.concat("Maria");

saludo.toUpperCase();

System.out.prinltn(saludo);

}

}

102

String Constant Pool

Es una zona especial de memoria en la que se almacena una

colección de referencias a objetos String. Se utiliza para mejorar

la eficiencia. Al ser los objetos String inmutables se pueden

compartir de forma segura por varias referencias y así evitamos

crear varios objetos Strings iguales.

Los Strings son objetos y como tales se crean en el Heap. Las

referencias en la String Constant Pool apuntan a objetos String

vivos en el Heap.

Cuando el compilador se encuentra con un literal String, comprueba

si ya existe en el pool un literal igual. Si se encuentra la nueva

referencia apunta al String existente.

Si no es así, se crea el nuevo objeto String y se incluye en el

pool.

Diferencias al crear un String

Existen diferencias sutiles al crear un String asignando a una

referencia un literal y al crear un objeto String utilizando la

palabra clave new. En ambos casos supondremos que no existe con

anterioridad el String en el pool.

Asignando un literal String:

1 String nombre = "Lola";

Estamos creando un objeto String Lola y una referencia:

103

Sin embargo, utilizando la palabra clave new:

1 String nombre = new String("Lola");

Estamos creando dos objetos y una referencia:

Métodos importantes String

charAt(int i): argumento comienza en cero.

concat(String s): añade la cadena que se pasa como parámetro.

replace(char a, char b): reemplaza el carácter a con el

carácter b.

substring(int ini, int end): devuelve la subcadena que

comienza en ini (empezar en 0) y acaba en end (se cuenta

desde 1).

trim(): devuelve la cadena sin espacios.

length(): devuelve la longitud de la cadena.

toLowerCase(): devuelve la cadena en minúsculas.

toUpperCase(): devuelve la cadena en mayúsculas.

104

equalsIgnoreCase(String s): devuelve si es igual sin importar

mayúscula o minúscula a la cadena que se pasa como parámetro.

Más información sobre la String Constant Pool: Strings, Literally

en JavaRanch.

En la siguiente entrada, las clases StringBuffer, StringBuilder y

los métodos más usuales

Resumen de las clases de Ficheros y E/S

Os dejo este diagrama de las clases que debemos conocer para el

examen.

Incluye los métodos más conocidos y los argumentos que admiten (F:

File, Str: String, Wr: Writer, R: Reader)

Algunas notas rápidas sobre las clases:

File: es una representación abstracta de nombres de ficheros y

directorios.

FileReader: lee ficheros de caracteres. Es una clase que opera a

bajo nivel. El método read lee todo el fichero, devuelve el tamaño

y lo devuelve en un array.

BufferedReader: es la clase que mejora la eficiencia de los

anteriores lectores. Lee largas cantidades de caracteres y guarda

lo datos en buffer. Tiene mejores métodos como readLine().

FileWriter: se usa para escribir en ficheros de caracteres. Su

método write() permite escribir caracteres o cadenas en un

fichero.

BufferedWriter: clase que mejora la eficiencia del anterior

escritor. Escribe grandes cantidades de caracteres en un fichero.

Tiene el método newLine().

105

PrintWriter: clase mejorada desde Java 5. Métodos nuevos como

format(), printf() y append() la hacen flexible y poderosa.

Console: provee métodos para leer y escribir en la consola.

Resumen Interfaz Set y clases implementadoras

En este diagrama os dejo las clases que deberéis conocer sobre la

interfaz Set, los métodos más usados y algunas anotaciones.

También algunos ejemplos para que comprobéis sus características.

1. Ejemplo HashSet

1

2

3

4

5

6

7

8

9

10

11

12

import java.util.*;

public class EjemploHashSet {

public static void main (String[] args)

{

HashSet<String> s1 = new HashSet <String>();

s1.add("Hola");

s1.add("Hola amigo");

s1.add("Encantado de conocerte");

s1.add("Se me hace tarde");

s1.add("Espero verte pronto");

s1.add("Adios");

106

13

14

15

16

17

18

19

20

21

22

23

s1.add("Adios"); //Intentamos añadir un duplicado.

System.out.println("Numero de elementos: " +

s1.size() + "\n");

Iterator i = s1.iterator();

while (i.hasNext()){

System.out.println(i.next());

}

}

}

2. Ejemplo LinkedHashSet

1

2

3

4

5

6

7

8

9

10

11

import java.util.*;

public class EjemploLinkedHashSet {

public static void main (String[] args)

{

LinkedHashSet<String> s2 = new LinkedHashSet

<String>();

s2.add("Hola");

s2.add("Hola amigo");

s2.add("Encantado de conocerte");

s2.add("Se me hace tarde");

s2.add("Espero verte pronto");

107

12

13

14

15

16

17

18

19

20

21

22

23

s2.add("Adios");

s2.add("Adios"); //Intentamos añadir un duplicado.

System.out.println("Numero de elementos: " +

s2.size() + "\n");

Iterator i = s2.iterator();

while (i.hasNext()){

System.out.println(i.next());

}

}

}

3. Ejemplo TreeSet

1

2

3

4

5

6

7

8

9

10

11

import java.util.*;

public class EjemploTreeSet {

public static void main (String[] args)

{

TreeSet<String> s3 = new TreeSet<String>();

s3.add("Hola");

s3.add("Hola amigo");

s3.add("Encantado de conocerte");

s3.add("Se me hace tarde");

s3.add("Espero verte pronto");

108

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

s3.add("Adios");

s3.add("Adios"); //Intentamos añadir un duplicado.

System.out.println("Numero de elementos: " +

s3.size() + "\n");

Iterator i = s3.iterator();

while (i.hasNext()){

System.out.println(i.next());

}

System.out.println ("\nlower(\"Encantado de

conocerte\"): "

+ s3.lower("Encantado de conocerte"));

System.out.println("floor(\"Encantado de

conocerte\"): "

+ s3.floor("Encantado de conocerte"));

System.out.println("higher(\"Encantado de

conocerte\"): "

+ s3.higher("Encantado de conocerte"));

System.out.println("ceiling(\"Encantado de

conocerte\"): "

+ s3.ceiling("Encantado de conocerte"));

// Devolvemos el set en orden descendente

NavigableSet<String> reverse = s3.descendingSet();

//Lo mostramos

Iterator r1 = reverse.iterator();

while (r1.hasNext()){

System.out.println(r1.next());

109

41

42

43

44

45

46

47

48

49

50

51

52

53

}

//Hacemos un pollFirst y pollLast

//Devuelve y elimina el primer elemento

System.out.println("\nreverse.pollFirst: " +

reverse.pollFirst());

//Devuelve y elimina el ultimo elemento

System.out.println("reverse.pollLast: " +

reverse.pollLast() + "\n");

//Vemos cómo queda el set

Iterator r2 = reverse.iterator();

while (r2.hasNext()){

System.out.println(r2.next());

}

}

}

110

Resumen Interfaz List y clases implementadoras

En este diagrama os dejo las clases que deberéis conocer sobre la

interfaz List, los métodos más usados y algunas anotaciones sobre

ellas.

1. Ejemplo ArrayList

1

2

3

import java.util.*;

public class EjemArrayList{

public static void main (String[] args){

111

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

ArrayList nombres = new ArrayList();

System.out.println("Elementos al inicio: " +

nombres.size());

nombres.add("Ana");

nombres.add("Bea");

nombres.add("Dalia");

nombres.add("Pedro");

nombres.add("Bea"); //comprobar que admite

duplicados

System.out.println("Contenido: " + nombres);

System.out.println("Elementos: " + nombres.size());

System.out.println(nombres.contains("Ana"));

nombres.remove("Ana"); //eliminamos un elemento

System.out.println("Contenido: " + nombres);

System.out.println("Elementos: " + nombres.size());

System.out.println(nombres.contains("Ana"));

nombres.add(3, "Ana"); //Añadir en posicion x

System.out.println("Contenido: " + nombres);

System.out.println("SubLista(1,4):" +

nombres.subList(1,4));

}

}

Resultado de la ejecución:

112

2. Ejemplo LinkedList

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

import java.util.*;

public class EjemLinkedList {

public static void main (String[] args){

LinkedList nombres = new LinkedList();

nombres.add("Ana");

nombres.add("Maria");

nombres.add("Pedro");

nombres.addFirst("Elena"); //insertamos al ppio.

nombres.addLast("Bea"); //insertamos al final.

nombres.add(2, "Lola"); //insertamos en posicion x

System.out.println("\nContenido: " + nombres);

System.out.println("getFirst(): " +

nombres.getFirst());

System.out.println("getLast(): " +

nombres.getLast());

System.out.println("\nHacemos peek(): " +

nombres.peek());

System.out.println("Contenido: " + nombres);

System.out.println("\nHacemos poll(): " +

nombres.poll());

System.out.println("Contenido: " + nombres);

113

24

25

26

System.out.println("\nHacemos offer(): " +

nombres.offer("Luis"));

System.out.println("Contenido: " + nombres);

}

}

Resultado de la ejecución:

Si queréis consultar algún método en concreto o verlos

todos: ArrayList, LinkedList, List.