martes, 9 de noviembre de 2021

JAVA Apuntes

 JAVA Apuntes

http://www.jtech.ua.es/j2ee/2003-2004/modulos/jhd/sesion01-apuntes.htm


1. Introducción a Java

 

Antes de concretar conceptos acerca del lenguaje Java, veremos algunas nociones y conceptos sobre programación orientada a objetos en general, y sobre diseño de programas mediante dicho paradigma.

 

1.1. Programación Orientada a Objetos

1.1.1. Objetos y clases

El elemento fundamental a la hora de hablar de programación orientada a objetos es el concepto de objeto en sí, así como el concepto abstracto de clase.

Un objeto es un conjunto de variables junto con los métodos relacionados con éstas. Contiene la información (las variables) y la forma de manipular la información (los métodos). Una clase es el prototipo que define las variables y métodos que va a emplear un determinado tipo de objeto, es la definición abstracta de lo que luego supone un objeto en memoria.

1.1.2. Campos, métodos y constructores

Toda clase u objeto se compone internamente de constructorescampos y/o métodos. Veamos qué representa cada uno de estos conceptos: un campo es un elemento que contiene información relativa a la clase, y un método es un elemento que permite manipular la información de los campos. Por otra parte, un constructor es un elemento que permite reservar memoria para almacenar los campos y métodos de la clase, a la hora de crear un objeto de la misma.

1.1.3. Herencia y polimorfismo

Con la herencia podemos definir una clase a partir de otra que ya exista, de forma que la nueva clase tendrá todas las variables y métodos de la clase a partir de la que se crea, más las variables y métodos nuevos que necesite. A la clase base a partir de la cual se crea la nueva clase se le llama superclase

Por ejemplo, podríamos tener una clase genérica Animal, y heredamos de ella para formar clases más específicas, como Pato , Elefante, o León. Estas clases tendrían todo lo de la clase padre Animal, y además cada una podría tener sus propios elementos adicionales. 

Una característica derivada de la herencia es que, por ejemplo, si tenemos un método dibuja(Animal a), que se encarga de hacer un dibujo del animal que se le pasa como parámetro, podremos pasarle a este método como parámetro tanto un Animal como un PatoElefante, o cualquier otro subtipo directo o indirecto de Animal. Esto se conoce como polimorfismo .

1.1.4. Modificadores de acceso

Tanto las clases como sus elementos (constructores, campos y métodos) pueden verse modificados por lo que se suelen llamar modificadores de acceso, que indican hasta dónde es accesible el elemento que modifican. Tenemos tres tipos de modificadores:

  • privado: el elemento es accesible únicamente dentro de la clase en la que se encuentra.
  • protegido: el elemento es accesible desde la clase en la que se encuentra, y además desde las subclases que hereden de dicha clase.
  • público: el elemento es accesible desde cualquier clase.

1.1.5. Clases abstractas e interfaces

Mediante las clases abstractas y los interfaces podemos definir el esqueleto de una familia de clases, de forma que los subtipos de la clase abstracta o la interfaz implementen ese esqueleto para dicho subtipo concreto. Por ejemplo, volviendo con el ejemplo anterior, podemos definir en la clase Animal el método dibuja() y el método imprime(), y que Animal sea una clase abstracta o un interfaz. 

Vemos la diferencia entre clase, clase abstracta e interfaz con este supuesto:

  • En una clase, al definir Animal tendríamos que implementar el código de los métodos dibuja() e imprime(). Las subclases que hereden de Animal no tendrían por qué implementar los métodos, a no ser que quieran redefinirlos para adaptarlos a sus propias necesidades.
  • En una clase abstracta podríamos implementar los métodos que nos interese, dejando sin implementar los demás (dejándolos como métodos abstractos). Dichos métodos tendrían que implementarse en las clases hijas.
  • En un interfaz no podemos implementar ningún método en la clase padre, y cada clase hija tiene que hacer sus propias implementaciones de los métodos. Además, las clases hijas podrían implementar otros interfaces.

 

1.2. Diseño Orientado a Objetos

Una vez explicados los conceptos básicos de programación orientada a objetos, veremos cómo utilizarlos para realizar el diseño de una aplicación. Para realizar dicho diseño utilizaremos los diagramas de clases de UML (Unified Modeling Language).

Explicaremos a continuación qué elementos se utilizan en estos diagramas, con ejemplos de cada caso, mostrando al final un ejemplo completo de diseño.

1.2.1. Representación de clases

Las clases e interfaces se representan mediante cajas con tres apartados: el nombre de la clase, los atributos o campos de la clase y sus métodos (donde se incluirían los métodos y los constructores).

 

Figura 1. Ejemplo de clase

Para representar clases abstractas, el nombre de la clase suele ponerse en cursiva.

1.2.2. Relaciones entre clases

Las clases e interfaces de un diagrama se pueden relacionar de varias formas. Mostramos a continuación los tipos de relaciones existentes.

Dependencia

Una relación de dependencia indica que un cambio en una clase puede afectar a otra. En este caso se dice que la segunda clase depende de la primera, y se representa con una flecha discontinua desde la clase dependiente a la clase independiente. Quiere decir que el elemento dependiente utiliza de alguna forma al independiente.

Figura 2. Relación de dependencia

En el ejemplo, la clase Ventana depende de la clase Evento, de forma que un cambio en el código del Evento podría afectar al comportamiento de la Ventana.

Generalización

Una relación de generalización conecta una clase general (clase padre o superclase) con una clase más especializada (clase hija o subclase), de forma que se puede decir que la clase hija es un o es una clase padre.

Figura 3. Relación de generalización

En el ejemplo, la clase Pato es una subclase de Animal. Vemos que la relación de generalización se utiliza para representar la herencia en la programación orientada a objetos. También se puede utilizar la generalización para los interfaces, bien para heredar de ellos y definir interfaces más concretos, o bien para definir un subtipo que implemente los métodos del interfaz.

Asociación

Una relación de asociación conecta objetos de una clase con objetos de otra. A este tipo de relaciones se le pueden definir tres atributos:

  • nombre: el nombre que queremos dar a la asociación, que definirá el tipo de asociación que es.
  • rol: cada una de las dos clases que intervienen en la asociación puede jugar un rol en la misma
  • multiplicidad: cada clase de la asociación puede tener cero, uno o n elementos asociados en la otra clase

Figura 4. Relación de asociación

En el ejemplo, las clases Persona Empresa están asociadas, de forma que una Persona trabaja en una Empresa ("trabaja en" sería el nombre de la relación de asociación). La Persona tiene el rol de empleado en esta relación, y la Empresa el rol de patrón. En cuanto a las multiplicidades, una Empresa puede tener una o varias Personas (multiplicidad 1..* ), y una Persona puede estar en cero o más Empresas (multiplicidad ).

Agregación

Una agregación es un tipo especial de asociación en el que una de las dos clases implicadas supone un todo, y la otra es una parte de ese todo. Se puede interpretar como una relación tiene un o tiene una.

Figura 5. Relación de agregación

Composición

Una composición es un tipo especial de agregación en la que la parte sólo puede estar relacionada con un todo. Podrá crearse después del todo, pero desaparecerá en cuanto desaparezca dicho todo.

Figura 6. Relación de composición

1.2.3. Modificadores de acceso

Los modificadores de ámbito público, protegido y privado también tienen su representación en los diagramas de clases, mediante los símbolos:

  • + se utiliza para indicar ámbito público
  • # se utiliza para indicar ámbito protegido
  • se utiliza para indicar ámbito privado

Figura 7. Modificadores en diagramas

1.2.4. Ejemplos completos

A continuación mostramos un par de ejemplos completos de diagramas, partiendo de un supuesto práctico:

Una empresa se dedica a la venta de diferentes tipos de artículos. Tiene dos tipos de clientes: clientes particulares y empresas. En ambos casos, almacena el nombre del cliente y la dirección. Para los clientes particulares, almacena su DNI y número de tarjeta de crédito (un dato que no debe ser accesible desde cualquier parte). Para las empresas almacena su CIF, y en este caso, se habilita una opción para facturar los pedidos de una sola vez para todo un mes, en lugar de ir pedido a pedido.

En cuanto a los pedidos, cada pedido tiene una fecha en la que se ha solicitado, y un importe total. Internamente, se compone de varias líneas de pedido, cada una para un artículo concreto, indicando su cantidad e importe subtotal. De cada artículo guardamos su código y su nombre. Nos interesan algunas tareas sencillas en los pedidos, como entregar un pedido, cerrar la entrega, y validar o borrar sus líneas de pedido.

Un posible diseño para este supuesto podría ser:

Figura 8. Un ejemplo de diseño completo

Se quiere planificar la estructura de cursos de ciertas academias. Cada academia se identifica por un CIF, y además nos interesa guardar su nombre y dirección. La academia ofrece una serie de cursos (de los que guardamos su nombre, horas y precio), y tiene contratados una serie de profesores, que pueden ser eventuales o indefinidos. De los profesores queremos guardar su DNI y nombre, y para los indefinidos nos interesa su antigüedad. Un profesor imparte uno o más cursos, y un curso puede impartirse por varios profesores. Por otra parte, se tienen una serie de alumnos (de los que nos interesan su DNI y nombre también), que están matriculados en una serie de cursos .

Nos interesa poder modificar las horas y precio de los cursos, para poder hacerlos flexibles, así como la antigüedad de los profesores. Por otra parte, resultaría interesante poder acceder al valor de todos los campos, salvo los que sean relativos a DNI o CIF, que sólo se tratarán internamente, y no serán accesibles desde fuera.

Un posible diseño para este problema sería el siguiente:

Figura 9. Un ejemplo de diseño completo

 

1.3. Introducción a Java

Java es un lenguaje de programación creado por Sun Microsystems para poder funcionar en distintos tipos de procesadores. Su sintaxis es muy parecida a la de C o C++, e incorpora como propias algunas características que en otros lenguajes son extensiones: gestión de hilos, ejecución remota, etc.

El código Java, una vez compilado, puede llevarse sin modificación alguna sobre cualquier máquina, y ejecutarlo. Esto se debe a que el código se ejecuta sobre una máquina hipotética o virtual, la Java Virtual Machine, que se encarga de interpretar el código (ficheros compilados .class) y convertirlo a código particular de la CPU que se esté utilizando (siempre que se soporte dicha máquina virtual).

Cuando se programa con Java, se dispone de antemano de un conjunto de clases ya implementadas. Estas clases (aparte de las que pueda hacer el usuario) forman parte del propio lenguaje (lo que se conoce como API (Application Programming Interface) de Java).

1.3.1. Componentes de un programa Java

En un programa Java podemos distinguir varios elementos:

Clases

Para definir una clase se utiliza la palabra reservada class, seguida del nombre de la clase:

class MiClase
{
   ...
}

Campos y variables

Dentro de una clase, o de un método, podemos definir campos o variables, respectivamente, que pueden ser de tipos simples, o clases complejas, bien de la API de Java, bien que hayamos definido nosotros mismos, o bien que hayamos copiado de otro lugar. 

int a;
Vector v;
MiOtraClase mc;

Métodos

Los métodos o funciones se definen de forma similar a como se hacen en C: indicando el tipo de datos que devuelven, el nombre del método, y luego los argumentos entre paréntesis:

void imprimir(String mensaje)
{
	... // Código del método
}

Vector insertarVector(Object elemento, int posicion)
{
	... // Código del método
}

Constructores

Podemos interpretar los constructores como métodos que se llaman igual que la clase, y que se ejecutan con el operador new para reservar memoria para los objetos que se creen de dicha clase:

MiClase()
{
	... // Código del constructor
}

MiClase(int valorA, Vector valorV)
{
	... // Código de otro constructor
}

No tenemos que preocuparnos de liberar la memoria del objeto al dejar de utilizarlo. Esto lo hace automáticamente el garbage collector. Aún así, podemos usar el método finalize() para liberar manualmente.

Paquetes

Las clases en Java se organizan (o pueden organizarse) en paquetes, de forma que cada paquete contenga un conjunto de clases. También puede haber subpaquetes especializados dentro de un paquete o subpaquete, formando así una jerarquía de paquetes, que después se plasma en el disco duro en una estructura de directorios y subdirectorios igual a la de paquetes y subpaquetes (cada clase irá en el directorio/subdirectorio correspondiente a su paquete/subpaquete).

Cuando queremos indicar que una clase pertenece a un determinado paquete o subpaquete, se coloca al principio del fichero la palabra reservada package seguida por los paquetes/subpaquetes, separados por '.' :

package paq1.subpaq1;
...
class MiClase {
...

Si queremos desde otra clase utilizar una clase de un paquete o subpaquete determinado (diferente al de la clase en la que estamos), incluimos una sentencia import antes de la clase (y después de la línea package que pueda tener la clase, si la tiene), indicando qué paquete o subpaquete queremos importar:

import paq1.subpaq1.*;
import paq1.subpaq1.MiClase;

La primera opción (*) se utiliza para importar todas las clases del paquete (se utiliza cuando queremos utilizar muchas clases del paquete, para no ir importando una a una). La segunda opción se utiliza para importar una clase en concreto.

Al importar, ya podemos utilizar el nombre de la clase importada directamente en la clase que estamos construyendo. Si no colocásemos el import podríamos utilizar la clase igual, pero al referenciar su nombre tendríamos que ponerlo completo, con paquetes y subpaquetes:

MiClase mc;				// Si hemos hecho el 'import' antes
paq1.subpaq1.MiClase mc;		// Si NO hemos hecho el 'import' antes

Existe un paquete en la API de Java, llamado java.lang, que no es necesario importar. Todas las clases que contiene dicho paquete son directamente utilizables. Para el resto de paquetes (bien sean de la API o nuestros propios), será necesario importarlos cuando estemos creando una clase fuera de dichos paquetes.

Aunque para una clase simple o un programa de uso interno sencillo no es necesario agrupar las clases en paquetes, sí es recomendable asignar un nombre de paquete a cada clase de una aplicación, para evitar que luego Java no pueda encontrarlas, debido a que no tienen paquete asignado.

Modificadores de acceso

Tanto las clases como los campos y métodos admiten modificadores de acceso, para indicar si dichos elementos tienen ámbito público, protegido privado. Dichos modificadores se marcan con las palabras reservadas public, protected private, respectivamente, y se colocan al principio de la declaración:

public class MiClase {
...
protected int b;
...
private int miMetodo(int b) {
...

El modificador protected implica que los elementos que lo llevan son visibles desde la clase, sus subclases, y las demás clases del mismo paquete que la clase.

Cada fichero Java que creemos debe tener una y sólo una clase pública (que será la clase principal del fichero). Dicha clase debe llamarse igual que el fichero. Aparte, el fichero podrá tener otras clases internas, pero ya no podrán ser públicas.

Por ejemplo, si tenemos un fichero MiClase.java, podría tener esta apariencia:

public class MiClase 
{
	...
}

class OtraClase
{
	...
}

class UnaClaseMas
{
	...
}

Otros modificadores

Además de los modificadores de acceso vistos antes, en clases, métodos y/o campos se pueden utilizar también estos modificadores:

  • abstract: elemento base para la herencia (los objetos subtipo deberán definir este elemento). Se utiliza para definir clases abstractas, y métodos abstractos dentro de dichas clases, para que los implementen las subclases que hereden de ella.
  • static: elemento compartido por todos los objetos de la misma clase. Con este modificador, no se crea una copia del elemento en cada objeto que se cree de la clase, sino que todos comparten una sola copia en memoria del elemento, que se crea sin necesidad de crear un objeto de la clase que lo contiene.
  • final: objeto final, no modificable ni heredable (se utiliza para definir constantes)
  • synchronized: para elementos a los que no se puede acceder al mismo tiempo desde distintos hilos de ejecución.

Estos modificadores se colocan tras los modificadores de acceso:

public abstract class Ejemplo 		// Clase abstracta para heredar de ella
{
	public static final TAM = 10;	// Constante estática de valor 10

	public abstract void metodo();	// Método abstracto a implementar
	
	public synchronized void otroMetodo()
	{
		... // Aquí dentro sólo puede haber un hilo a la vez
	}
}

NOTA IMPORTANTE: si tenemos un método estático (static), dentro de él sólo podremos utilizar elementos estáticos (campos o métodos estáticos), o bien campos y métodos de objetos que hayamos creado dentro del método. Por ejemplo, si tenemos:

public class UnaClase
{
	public int a;

	public static int metodo()
	{
		return a + 1;
	}
}

dará error, porque el campo a no es estático, y lo estamos utilizando dentro del método estático. Para solucionarlo tenemos dos posibilidades: definir a como estático (si el diseño del programa lo permite), o bien crear un objeto de tipo UnaClase en el método, y utilizar su campo a (que ya no hará falta que sea estático, porque hemos creado un objeto y ya podemos acceder a su campo a):

public class UnaClase
{
	public int a;

	public static int metodo()
	{
		UnaClase uc = new UnaClase();
		// ... Aquí haríamos que uc.a tuviese el valor adecuado
		return uc.a + 1;
	}
}

Ejecución de clases: método main

En las clases principales de una aplicación (las clases que queramos ejecutar) debe haber un método main con la siguiente estructura:

public static void main(String[] args)
{
	... // Código del método
}

Dentro pondremos el código que queramos ejecutar desde esa clase. Hay que tener en cuenta que main es estático, con lo que dentro sólo podremos utilizar campos y métodos estáticos, o bien campos y métodos de objetos que creemos dentro del main.

Ejemplo completo

Con todo lo anterior, podríamos tener una clase como:

package paquete1.subpaquete1;

import otropaquete.MiOtraClase;
import java.util.Vector;

public class MiClase 
{
	public int a;
	public Vector v;
	private MiOtraClase mc;

	public MiClase()
	{
		... // Código del constructor
	}

	public MiClase(int valorA, Vector valorV)
	{
		... // Código de otro constructor
	}

	void imprimir(String mensaje)
	{
		... // Código del método
	}

	public Vector insertarVector(Object elemento, int posicion)
	{
		... // Código del método
	}
}

Y podríamos definir una instancia de esa clase, y utilizar sus campos y métodos. Para ello utilizamos el operador new:

import paquete1.subpaquete1.*;
import java.util.*;

public class OtraClase
{
	public void metodo()
	{
		MiClase mc;
		mc = new MiClase (1, new Vector());
		mc.a++;
		mc.insertarVector("hola", 0);
	}
	...
}

1.3.2. Otras posibilidades

Herencia 

Cuando queremos que una clase herede de otra, se utiliza al declararla la palabra extends tras el nombre de la clase, para decir de qué clase se hereda. Para hacer que Pato herede de Animal:

class Pato extends Animal

Con esto automáticamente Pato tomaría todo lo que tuviese Animal (aparte, Pato puede añadir sus características propias). Si Animal fuese una clase abstracta, Pato debería implementar los métodos abstractos que tuviese.

Punteros this y super

this se usa para hacer referencia a los miembros de la propia clase. Se utiliza cuando hay otros elementos con el mismo nombre, para distinguir :

public class MiClase 
{
   int i;
   public MiClase (int i) 
   {
       this.i = i; 	// i de la clase = parametro i
   }
}

super se usa para llamar al mismo elemento en la clase padre. Si la clase MiClase tiene un método Suma_a_i(...), lo llamaríamos desde esta otra clase con:

public class MiNuevaClase extends MiClase 
{
   public void Suma_a_i (int j) 
   {
      i = i + (j / 2);
      super.Suma_a_i (j);
   }
}

Interfaces

Ya hemos visto cómo definir clases normales, y clases abstractas. Si queremos definir un interfaz, se utiliza la palabra reservada interface, en lugar de class, y dentro declaramos (no implementamos), los métodos que queremos que tenga la interfaz:

public interface MiInterfaz 
{
	public void metodoInterfaz();
	public float otroMetodoInterfaz();
}

Después, para que una clase implemente los métodos de esta interfaz, se utiliza la palabra reservada implements tras el nombre de la clase:

public class UnaClase implements MiInterfaz 
{
	public void metodoInterfaz()
	{
		... // Código del método
	}

	public float otroMetodoInterfaz()
	{
		... // Código del método
	}
}

Notar que si en lugar de poner implements ponemos extends, en ese caso UnaClase debería ser un interfaz, que heredaría del interfaz MiInterfaz para definir más métodos, pero no para implementar los que tiene la interfaz. Esto se utilizaría para definir interfaces partiendo de un interfaz base, para añadir más métodos a implementar.

Una clase puede heredar sólo de otra única clase, pero puede implementar cuantos interfaces necesite:

public class UnaClase extends MiClase implements MiInterfaz, MiInterfaz2, MiInterfaz3
{
	...
}

1.3.3. Aspectos de sintaxis

Tipos de datos

Se tienen los siguientes tipos de datos simples: 

  • Enterosbyte, short, int, long
  • Realesfloat, double
  • Caractereschar
  • CadenasString (equivalente al char* de C). 
  • Lógicosboolean (equivaldría al 0/1 de C, con valores false/true)

Además, se tienen los objetos complejos, que son las clases de la API de Java, y se pueden crear otros objetos complejos, todos los cuales serán subtipos directos o indirectos de la clase padre general Object.

Cadenas de caracteres

En realidad, las cadenas (String) son tipos complejos (es un subtipo de Object), pero se trata como si fuera un tipo simple. Podemos utilizar el operador de concatenación (+) para concatenar cadenas entre sí, y también cadenas con objetos que puedan convertirse directamente a cadena, como enteros, reales, lógicos, etc:

int a = 27;
String c = "El numero elegido es " + 27 + " unidades";

Arrays

Se definen arrays o conjuntos de elementos de forma similar a como se hace en C. Hay 2 métodos: definiendo su tamaño, o definiendo sus elementos:

int a[] = new int [10];
String s[] = {"Hola", "Adios"};

No pueden crearse arrays estáticos en tiempo de compilación (int a[8];), ni rellenar un array sin definir previamente su tamaño con el operador new. La función miembro length se puede utilizar para conocer la longitud del array:

int a [][] = new int [10] [3];
a.length;        // Devolvería 10
a[0].length;     // Devolvería 3

Los arrays empiezan a numerarse desde 0, hasta el tope definido menos uno (como en C).

Comentarios

// comentarios para una sola línea

/* comentarios de 
   una o más líneas */

/** comentarios de documentación para javadoc, 
    de una o más líneas */

Operadores

Se tienen los siguientes operadores, ordenados por precedencia:

 

OperadorEjemploDescripción
.a.lengthCampo o método de objeto
[ ]a[6]Referencia a elemento de array
( )(a + b)Agrupación de operaciones
++ ,  --a++; b--Autoincremento / Autodecremento de 1 unidad
!, ~!a ; ~bNegación / Complemento
instanceofa instanceof TipoDatoIndica si es del tipo TipoDato
*, /, %a*b; b/c; c%aMultiplicación, división y resto de división entera
+, -a+b; b-cSuma y resta
<<, >>a>>2; b<<1Desplazamiento de bits a izquierda y derecha
<, >, <=, >=, ==, !=a>b; b==c; c!=aComparaciones (mayor, menor, igual, distinto...)
&, |, ^a&b; b|cAND, OR y XOR lógicas
&&, ||a&&b; b||cAND y OR condicionales
?:a?b:cCondicional: si entonces b , si no c
=, +=, -=, *=, /= ...a=b; b*=cAsignación. a += b equivale a (a = a + b)

Control de flujo

  • Toma de decisiones: se tienen las sentencias ifif...else... y switch, similares a C.
  • Bucles: se tienen los bucles forwhile y do...while, similares a C.
  • Sentencias de ruptura: al igual que en C, se tienen las sentencias break y continue para romper el flujo de los bucles, y la sentencia return para salir de los métodos, devolviendo o sin devolver un valor, según corresponda.

Otros elementos

En Java se tiene también un término para referenciar un valor nulo, equivalente al NULL de C, pero en minúsculas:

String a = null;
...
if (a == null)...

Sólo podemos asignar valor null a objetos complejos (subtipos de Object), no a tipos simples.

1.3.4. Normas de estilo

Se proponen a continuación algunas pautas que conviene seguir para hacer que nuestro código sea correcto, y nuestros programas sean sencillos de comprender y mantener.

Principios generales

  • Respetar el estilo de un programa cuando se modifique (no cambiar su estilo, ni añadir lo nuevo con otro estilo)
  • Procurar que el programa se comporte de forma predecible y consistente, y no dé sorpresas al usuario. Para ello el diseño, implementación y documentación deben tener una serie de características:
    • Simplicidad: construir clases y métodos simples
    • Claridad: que cada elemento tenga un objetivo bien definido y explicado
    • Completitud: proporcionar la funcionalidad mínima que espera el usuario, y documentar todas las funcionalidades y características
    • Consistencia: entidades similares deben comportarse de forma similar, y entidades diferentes deben comportarse de forma diferenciada. Se deben utilizar estándares en la medida de lo posible
    • Robustez: proporcionar documentación completa sobre posibles errores y excepciones

Convenciones de formato

  • Indentar el código uniformemente para cada bloque (tras cada llave, cada bucle, cada if...). En algunos libros se recomienda no usar tabuladores, sino espacios, pues los tabuladores dependen de cada editor.
  • Limitar la anchura de las líneas de código a entre 80 y 132 caracteres (para poderse imprimir si es necesario). Para ello:
    • No declarar varias variables en una línea si ésta resulta muy larga (utilizar varias declaraciones separadas en varias lineas)
    • Si una sentencia es muy larga, descomponerla en varias más simples y cortas
    • Si no se cumple nada de lo anterior, separar por lo primero que se encuentre de:
      • Separar detrás de las comas
      • Separar tras cada operador de menor preferencia
  • Utilizar líneas en blanco para separar:
    • Secciones lógicas dentro de un método
    • Cada miembro de una clase o interfaz
    • Cada clase o interfaz de un fichero
  • Utilizar espacio para separar:
    • Palabras de los ) o } que les precedan, y los (, { de las palabras que les precedan
    • Operadores binarios

Convenciones de nombrado

  • Utilizar nombres con significado. Evitar utilizar nombres cortos, abreviaturas o valores constantes directos si no se sabe qué pueden ser. La excepción a esto está en variables temporales (para bucles "for", por ejemplo). Evitar también nombres demasiado largos.
  • Poner en mayúscula sólo las letras que separen palabras dentro del nombre de la variable, incluso si hay acrónimos. Por ejemplo: loadXmlDocument = OK, loadXMLDocument = NO. 
    La excepción a esto está en los nombres de constantes, que van con todas las letras en mayúscula. Si el acrónimo está al principio del nombre, irán todas sus letras en minúscula (xmlDocument, p. ej.)
  • No utilizar nombres que sólo se diferencien en mayúsculas/minúsculas con otros ya existentes

Nombrado de paquetes

  • Utilizar el inverso en minúsculas del dominio de nuestra empresa para crear paquetes que se quieran distribuir a otras organizaciones. Por ejemplo, si nuestra empresa tiene el dominio miempresa.com, y queremos distribuir el paquete "paquete1", lo distribuiremos con el nombre "com.miempresa.paquete1"
  • Utilizar palabras simples y en minúscula para los nombres de paquetes
  • Utilizar el mismo nombre para una nueva versión de un paquete solo si la nueva versión es compatible con la anterior, en caso contrario utilizar otro paquete diferente

Nombrado de tipos

  • Poner en mayúsculas la primera letra de cada palabra de un nombre de clase o interfaz: InputStreamFileInputStream, etc
  • Utilizar nombres (sustantivos) cuando le estemos dando un nombre a una clase.
  • Pluralizar los nombres de clases que agrupen atributos relacionados, servicios estáticos o constantes: SwingConstants, etc.
  • Utilizar nombres o adjetivos cuando estemos nombrando interfaces: ActionListenerRunnable.

Nombrado de métodos

  • Utilizar minúsculas para la primera palabra, y poner sólo en mayúscula la primera letra de las siguientes palabras para los nombres de métodos (loadXmlDocument()getName(), etc).
  • Utilizar verbos para nombrar los métodos
  • Utilizar las convenciones de JavaBeans para nombrar métodos que operan sobre el valor de campos: métodos getCampo() para obtener el valor, setCampo() para establecer el valor, e isCampo() si el valor es booleano, para obtener su valor.

Nombrado de variables

  • Utilizar minúsculas para la primera palabra, y poner sólo en mayúscula la primera letra de las siguientes palabras para los nombres de variables, como se ha explicado anteriormente.
  • Utilizar nombres para nombrar variables
  • Pluralizar los nombres de colecciones
  • Definir un conjunto de nombres estándar para cierto tipo de variables temporales. P. ej., caracteres (c, d), coordenadas (x, y, z), excepciones (e), gráficos (g), etc. Estos nombres pueden ser cortos, de una sola letra, porque se suele comprender a qué se refieren, y las variables son de uso temporal
  • Identificar los campos con "this" para diferenciarlos de variables locales

Nombrado de parámetros

  • Cuando un constructor o un método set...() asigna un valor a un campo, llamar al parámetro igual que al campo. Por ejemplo, si tenemos un campo edad, y un método setEdad(int valor), el parámetro valor no debería llamarse valor, sino edad.

Nombrado de constantes

  • Utilizar mayúsculas, y separar cada palabra con el subrayado '_'

Convenciones de documentación

  • Escribir documentación para quien tenga que utilizar el programa y para quien tenga que mantenerlo
  • Asegurarse de que los comentarios están acordes al código que comentan (si modificamos el código, modificar los comentarios también)
  • Utilizar voz activa, y evitar palabras innecesarias
  • Utilizar comentarios de documentación (javadoc) para describir la interfaz de programación (colocarlos antes de cada clase, interfaz, método, constructor o campo)
  • Se recomienda escribir todos los comentarios de documentación antes de escribir el resto de código de los métodos, constructores, etc.
  • Realizar ficheros descriptivos de los paquetes (ficheros package.html colocados en los directorios javadoc de los paquetes)
  • Utilizar un formato consistente para estos comentarios:
    • Que la '/' de la primera línea se alinee con el objeto que describe
    • Que los '*' de las siguientes líneas se alineen con el primer * de la primera línea
    • Utilizar un solo espacio para separar el * de lo que viene a continuación en la línea
    • Utilizar una línea en blanco para separar la descripción de cualquier tag de javadoc que se utilice
    • Terminar con un */ alineado con los asteriscos de líneas anteriores:
/**
 * Comentario descriptivo
 *
 * @param loquesea
 * @return loquesea
 */
  • Utilizar tags @link para relacionar las partes de la documentación que creamos conveniente: {@link #enlace}
  • Encerrar palabras clave, identificadores y constantes entre tags <code>...</code>
  • Encerrar código fuente entre etiquetas <pre>...</pre>. Procurar incluir código ejemplo en los comentarios que lo necesiten
  • Establecer siempre el mismo orden para los tags de javadoc. Sun recomienda:
    • Para descripciones de clases e interfaces: @author, @version, (linea en blanco), @see, @since, @deprecated
    • Listar los autores en orden cronológico, con el creador de la clase o interfaz en primer lugar
    • Para descripciones de métodos: @param, @return, @exception, (linea en blanco), @see, @since, @deprecated
    • Utilizar @exception para cada excepción de tipo checked listada en el throws), y para cada no checked que se pueda esperar. Listarlas en orden alfabético
    • En descripciones de campos: @see, @since, @deprecated
    • Ordenar multiples tags @see según la distancia a la situación del comentario actual
  • Utilizar tercera persona en los comentarios
  • Diferenciar claramente con los comentarios los métodos sobrecargados (métodos que se llamen como otros existentes, pero con diferentes parámetros).
  • Omitir el sujeto en las descripciones principales de acciones o servicios
  • Omitir sujeto y verbo en las descripciones principales de cosas (clases, interfaces o campos que representan cosas)
  • Documentar precondiciones, postcondiciones y condiciones invariantes (condiciones que deben permanecer igual antes y después)
  • Documentar deficiencias de la implementación
  • Documentar tareas de sincronización
  • Utilizar comentarios de varias lineas /* ... */ para esconder código sin eliminarlo
  • Utilizar comentarios de una linea // ... para explicar detalles de la implementación. No escribir comentarios que repitan lo que se ve que el código hace claramente, sino comentarios que tengan información útil.
  • Evitar en la medida de lo posible comentarios que van al final de una línea de codigo. Utilizarlos sólo para explicar variables locales: int a;  // La variable a...
  • Establecer palabras clave para marcar tareas por terminar o resolver ("TODO:", "UNRESOLVED:", etc)

Convenciones de programación

  • Considerar definir las clases que representen datos fundamentales como finales
  • Procurar construir nuestras propias clases con el mínimo número de dependencias con otros objetos que no sean tipos simples ni tipos nativos de Java
  • Definir clases y métodos cortos
  • Procurar que los subtipos de una clase sean compatibles con la superclase (procurar no redefinir los métodos heredados, siempre que sea posible)
  • Hacer todos los campos privados (acceder a ellos mediante métodos get/set)
  • Definir clases o métodos que especialicen aquellas clases o métodos que se apoyan en tipos Object, y hacerlas que trabajen con tipos concretos. Por ejemplo, si una clase tiene una clase que maneja una lista de elementos, y para añadirlos tenemos un método:
addItem(Object ob) 

y queremos hacer una clase que sea una lista de cadenas, haremos una clase con un método 

addItem(String s)

que internamente utilice el otro método.

  • Encapsular las enumeraciones como clases, para poder compararlas mejor
  • Convertir expresiones no triviales que se repitan en el código en un solo método reutilizable
  • Procurar utilizar llaves en sentencias de control de flujo, aunque sólo tengan una instrucción dentro, para evitar ambigüedades y errores de anidamiento
  • Clarificar el orden de las operaciones con paréntesis
  • Colocar un break siempre en el último case de un switch (por si luego se añaden más cases a continuación, y nos olvidamos de ponerlo)
  • Utilizar equals() y no == para comparar objetos complejos
  • Hacer que los constructores públicos siempre construyan objetos que queden en un estado válido, y no en uno intermedio (para aquellas construcciones que después requieren ciertas inicializaciones, utilizar constructores protegidos o privados)
  • No llamar a métodos no finales en un constructor (las subclases podrían redefinirlos, y eso podría ocasionar que el objeto construido no quedase en un estado válido).
  • Utilizar constructores anidados para ahorrar código: si tenemos dos constructores:
Constructor(String nombre, int entero)
{
	this.nombre = nombre;
	this.entero = entero;
}

Constructor(String nombre)
{
	this.nombre = nombre;
}	

Hacer que el segundo llame al primero con valores por defecto en los parámetros que no tiene:

Constructor(String nombre)
{
	this(nombre, VALOR_DEFECTO);
}	
  • Utilizar códigos de retorno para indicar cambios de estado (devolver códigos de estado en los métodos que puedan devolverlos, como por ejemplo al leer un fichero, para ver si se ha leído bien o mal, etc)
  • Chequear las precondiciones que necesita un método al principio del mismo, antes de que haya hecho nada, y las postcondiciones al final, justo antes de salir o devolver el valor que sea. Para este chequeo, podemos hacer que los métodos reales sean protegidos, y luego hacer unos públicos finales en paralelo que chequeen las precondiciones, llamen al método protegido asociado, y chequeen las postcondiciones. Las subclases simplemente deberían redefinir (si lo necesitan) los métodos protegidos.
  • Controlar las aserciones mediante código muerto: una aserción nos permite verificar una parte de código comprobando que se deben cumplir ciertas reglas en dicha parte. Cuando probamos un programa, podemos colocar aserciones por cualquier parte, pero luego estas aserciones consumen tiempo, y es necesario no ejecutarlas cuando el programa ya ha sido comprobado. Para dejar de ejecutarlas, se deben colocar en líneas de código muerto, líneas que permitan al compilador no ejecutarlas de forma sencilla, como por ejemplo:
if (true)
	aserción	

Cambiando el flag del if a false, dejará de ejecutarse la aserción. También podemos colocar las aserciones detrás de un return, etc.

  • No inicializar objetos hasta que no se necesiten
  • Reinicializar y reutilizar objetos en lugar de crear nuevos

Convenciones de paquetes

  • Colocar los tipos que se suelan utilizar conjuntamente, o que dependan unos de otros, en un mismo paquete.
  • Separar las clases volátiles (que cambien su código con asiduidad) de las clases que tengamos ya estables, y dejarlas en paquetes diferentes
  • Hacer que un paquete sólo dependa de paquete(s) más estable(s) que él
  • Maximizar la abstracción para maximizar la estabilidad: construir paquetes con clases abstractas e interfaces donde englobar conceptos estables y de alto nivel, y a partir de ahí derivar en otros paquetes más concretos y específicos

1.3.5. Programas Básicos en Java

Veamos ahora algunos ejemplos de programas en Java.

  • Ejemplo: El siguiente ejemplo muestra un texto por pantalla (muestra "Mi programa Java"): Código
  • Ejemplo: El siguiente ejemplo toma dos números (un entero y un real) y devuelve su suma: Código
  • Ejemplo: El siguiente ejemplo resuelve el teorema de pitágoras (obtiene una hipotenusa a partir de dos catetos): Código
  • Ejemplo: El siguiente ejemplo devuelve todos los números primos que encuentra hasta un número determinado: Código
  • Ejemplo: El siguiente ejemplo muestra cómo utilizar herencia y clases abstractas. Define una clase abstracta Persona, de la que hereda la clase Hombre. La clase Anciano a su vez hereda de la clase Hombre. En la clase Ejemplo5 se tiene el método main(), que muestra resultados de llamadas a todas las clases. Compilando esta clase se compila todo el ejemplo: Código

No hay comentarios:

Publicar un comentario