Implementación de reglas de construcción en Java

RECU-0745 (Recurso Ejemplo)

Descripción

A continuación se muestran ejemplos que implementan las reglas de construcción en Java

Ejemplos

Protección de métodos contra la sobreescritura

Para proteger a un método contra la sobreescritura se declara como final. Un ejemplo puede ser el siguiente:

class AlgoritmodeAjedrez {
   ...
   final void siguienteMovimiento(Pieza piezaMovida,
         PosicionenTablero nuevaPosicion) {
 }
    ...
}

Modificador "final" en variables

El código que se muestra a continuación daría un error ya que no se le puede asignar otro valor a la variable "cadena"

public class Ejemplo {
   public static void main(String[] args) {
      final String cadena = "Hola";
      cadena = new String("Adios");
    }
 }

Uso del modificador "static"

Podemos ver unos casos de uso del modificador "static"

// Definir constantes final. Si no las hacemos static, cada instancia de la clase 
// tiene su propia copia de la constante, lo que es un desperdicio de memoria. public static final double PI = 3.1416; // Definir atributos que interese que sean compartidos entre todas las instancias. public class Clase { protected static int contador=0; public Clase () { contador++; ... } ... }

Importación de paquetes

Vemos cómo se importan los paquetes en Java

// Importación que cumple las buenas prácticas en Java
import java.awt.peer.CanvasPeer;

// Importación que no cumple las buenas prácticas
import java.awt.peer.*;

Manejo de los modificadores en las sentencias

Los distintos manejadores de sentencias existentes en Java son:

  • public: permite a acceder al elemento desde cualquier clase, independientemente de que esta pertenezca o no al paquete en el que se encuentra el elemento.
  • protected: indica que los elementos sólo pueden ser accedidos desde su mismo paquete y desde cualquier clase que extienda la clase en que se encuentra, independientemente de si ésta se encuentra en el mismo paquete o no.
  • private: es el modificador más restrictivo y especifica que los elementos que lo utilizan sólo pueden ser accedidos desde la clase en la que se encuentran.
  • abstract: indica que no se provee una implementación para un cierto método, sino que la implementación vendrá dada por las clases que extiendan la clase actual. Una clase que tenga uno o más métodos abstract debe declararse como abstract a su vez.
  • static: sirven para crear miembros que pertenecen a la clase, y no a una instancia de la clase. Esto implica, entre otras cosas, que no es necesario crear un objeto de la clase para poder acceder a estos atributos y métodos.
  • final: indica que una variable, método o clase no se va a modificar, lo cuál puede ser útil para añadir más semántica, por cuestiones de rendimiento, y para detectar errores. Si una variable se marca como final, no se podrá asignar un nuevo valor a la variable. Si una clase se marca como final, no se podrá extender la clase. Si es un método el que se declara como final, no se podrá sobreescribir.
  • transient: utilizado para indicar que los atributos de un objeto no son parte persistente del objeto o bien que estos no deben guardarse y restaurarse utilizando el mecanismo de serialización estándar.
  • volatile: es uno de los mecanismos de sincronización de Java. Se utiliza este modificador sobre los atributos de los objetos para asegurar que el valor siempre está actualizado, a pesar de ser utilizado por varios hilos de ejecución. Este modificador no proporciona atomicidad, lo que puede hacer que sea más complicado de utilizar.
  • synchronized: es otro de los mecanismos de sincronización de Java. Al igual que volatile, se utiliza sobre los atributos para asegurar que el valor siempre estará actualizado a pesar de ser modificado por varios hilos. Este modificador sí proporciona atomicidad y se utiliza sobre bloques de código o métodos, en lugar de usarse sobre variables como volatile.
  • native: es un modificador utilizado cuando un determinado método está escrito en un lenguaje distinto a Java, normalmente C, C++ o ensamblador para mejorar el rendimiento. La forma más común de implementar estos métodos es utilizar JNI (Java Native Interface).
  • strictfp: Su uso sobre una clase, interfaz o método sirve para mejorar su portabilidad haciendo que los cálculos con números flotantes se restrinjan a los tamaños definidos por el estándar de punto flotante de la IEEE (float y double), en lugar de aprovechar toda la precisión que la plataforma en la que estemos corriendo el programa pudiera ofrecernos. No es aconsejable su uso a menos que sea estrictamente necesario.

Uso de método clone()

El siguiente ejemplo muestra cómo se implementa el método clone() en la clase Punto y cómo se utiliza posteriormente

// De esta manera se sobrescribe el método clone() en la clase Punto
public class Punto implements Cloneable{
    private int x;
    private int y;
//constructores ...

    public Object clone(){
        Object obj=null;
        try{
            obj=super.clone();
        }catch(CloneNotSupportedException ex){
            System.out.println(" no se puede duplicar");
        }
        return obj;
    }
//otras funciones miembro
}

// Para crear un objeto pCopia que es una copia de otro objeto punto se escribe.

        Punto punto=new Punto(20, 30);
        Punto pCopia=(Punto)punto.clone();

Sobrescritura de los métodos equals() y hashCode()

En la clase java.lang.Object (y por lo tanto, por herencia, en todas las demás clases) tenemos métodos que a veces olvidamos y que son importantes:

  • public boolean equals(Object o)
  • public int hashCode()

Estos métodos son especialmente importantes si vamos a guardar nuestros objetos en cualquier tipo de colección: listas, mapas… y más aun si los objetos que vamos a guardar en la colección son serializables.

Estos métodos tienen formas explicitas de cómo hay que implementarlos. Sobrescribir estos métodos puede parecer simple, pero en realidad hay muchas formas de hacerlo incorrectamente lo que nos puede llevar a muchos quebraderos de cabeza. Podemos ver cómo sobrescribir estos métodos aquí.

Comunicación entre hilos

Se muestra el ejemplo del productor y el consumidor, donde uno de los hilos produce datos y otro los consume. En él, el productor tiene que esperar a que el consumidor haya terminado, para empezar a producir más datos

class Q {
	int n;
    boolean valueSet = false;
	
    synchronized int get() {
		if (!valueSet){
			try {
				wait();
			} catch(InterruptedException e);
        }
		System.out.println("Obtenido: " + n);
		valueSet = false;
		notify();
		return n;
    }
		
    synchronized void put(int n) {
		if (valueSet){
	    	try {
				wait();
			} catch(InterruptedException e);
		}
		this.n = n;
		valueSet = true;
		System.out.println("Colocado: " + n);
		notify();
    }
}

class Producer implements Runnable {
	Q q;
	
	Producer (Q q) {
		this.q =  q;
		new Thread(this, "Producer").start();
	}
	
	public void run() {
		int y = 0;
		while(true) {
			q.put(i++);
		}
	}
}

class Consumer implements Runnable {
	Q q;
	
	Consumer(Q q) {
		this.q = q;
		new Thread(this, "Consumer").start();
	}
	
	public void run() {
		while(true) {
				q.get();
		}
	}
}
		
class PC {
	public static void main(String args[]) {
		Q q = new Q();
		new Producer(q);
		new Consumer(q);
	}
}

Contenidos relacionados