1. Constantes y enumeraciones
1.1 Constantes
Una constante es una variable del sistema que mantiene un valor inmutable a lo largo de toda la vida del programa. Las constantes en Java se definen mediante la palabra reservada final.
En los programas que generemos usualmente intervendrán constantes: valores matemáticos como el número Pi, o valores propios de programa que nunca cambian.
Ejemplo de un programa en el que queremos usar una constante como el número Pi.
// Ejemplo aprenderaprogramar.com
public class Calculadora {
private double PI = 3.1416;
public void mostrarConstantePi () { System.out.println (PI); }
… constructor, métodos, … código de la clase … }
No obstante, una declaración de este tipo presenta varios problemas. En primer lugar, lo declarado no funciona realmente como constante, sino como variable con un valor inicial. Prueba a establecer this.PI = 22; dentro del método y verás que es posible, porque lo declarado es una variable, no una constante. En segundo lugar, cada vez que creamos un objeto de tipo calculadora estamos usando un espacio de memoria para almacenar el valor 3.1416.
Así, si tuviéramos diez objetos calculadora, tendríamos diez espacios de memoria ocupados con la misma información, lo cual resulta ineficiente. Para resolver estos problemas, podemos declarar constantes en Java usando esta sintaxis:
CaracterPublico/Privado static final TipoDeLaConstante =
valorDeLaConstante;
En esta declaración intervienen dos palabras clave cuyo significado es importante:
- static: los atributos miembros de una clase pueden ser atributos de clase o atributos de instancia; se dice que son atributos de clase si se usa la palabra clave static: en ese caso la variable es única para todas las instancias (objetos) de la clase (ocupa un único lugar en memoria). A veces a las variables de clase se les llama variables estáticas. Si no se usa static, el sistema crea un lugar nuevo para esa variable con cada instancia (la variable es diferente para cada objeto). En el caso de una constante no tiene sentido crear un nuevo lugar de memoria por cada objeto de una clase que se cree. Por ello, es adecuado el uso de la palabra clave static. Cuando usamos “static final” se dice que creamos una constante de clase, un atributo común a todos los objetos de esa clase.
- final: en este contexto indica que una variable es de tipo constante, no admitirá cambios después de su declaración y asignación de valor final, esta determina que un atributo no puede ser sobreescrito o redefinido. Es decir, no funcionará como una variable “tradicional”, sino como una constante. Toda constante declarada con final ha de ser inicializada en el mismo momento de declararla. final también se usa como palabra clave en otro contexto: una clase final (final) es aquella que no puede tener clases que la hereden. Cuando se declaran constantes es muy frecuente que los programadores usen letras mayúsculas (como práctica habitual que permite una mayor claridad en el código), aunque no es obligatorio.
Una declaración en cabecera de una clase como private final double PI = 3.1416; podríamos interpretarla como una constante de objeto. Cada objeto tendrá su espacio de memoria con un contenido invariable. Una declaración en cabecera de una clase como private static final double Pi = 3.1416; la interpretamos como una constante de clase. Existe un único espacio de memoria, compartido por todos los objetos de la clase, que contiene un valor invariable.
Veamos ejemplos de uso:
// Ejemplo aprenderaprogramar.com
// Sintaxis: CaracterPublico/Privado static final TipoDeLaConstante =
valorDeLaConstante;
private static final float PI = 3.1416f; //Recordar f indica que se trata de
un float
private static double PI = 3.1416;
public static final String passwd = "jkl342lagg";
public static final int PRECIO_DEFAULT = 100;
Cuando usamos la palabra clave static, la declaración de la constante ha de realizarse obligatoriamente en cabecera de la clase, junto a los campos (debajo de la signatura de clase). Es decir, un atributo de clase hemos de declararlo en cabecera de clase.
Si tratamos de incorporarlo en un método obtendremos un error. Por tanto, dentro del método main (que es un método de clase al llevar incorporado static en su declaración) no podemos declarar constantes de clase.
Por otro lado, final sí puede ser usado dentro de métodos y también dentro de un método main.Por ejemplo una declaración como final String psswd = “mt34rsm8” es válida dentro de un método.
En resumen: en cabecera de clase usaremos static final para definir aquellas variables comunes a todos los objetos de una clase.
1.2 Enumeraciones
Las enumeraciones son tipos de datos que pueden contener una lista de posibles valores o constantes. Esta es una definición bastante simplista, ya que tal y como podremos ver, se trata de una utilidad muy flexible y poderosa.
Se puede definir una enumeración de forma independiente como si se tratase de cualquier otra clase, salvo que sustituiremos la palabra reservada class por la palabra enum. Para definir un nuevo enumerado se utiliza la siguiente construcción:
[visibilidad][modific] enum Nombre [valor1,valor2,…]
Donde
visibilidad: public,protected,private
modific: static
Ejemplo de enumeración como parámetros de entrada en métodos:
public enum Dia {
LUNES,
MARTES,
MIERCOLES,
JUEVES,
VIERNES,
SABADO,
DOMINGO
}
public class AgendaActividades {
public void crearActividad ( String nombreActividad, Dia diaSemana) {
System.out.println (“Crear la actividad” + nombreActividad + “para el día” +
diaSemana.name() );
Switch(diaSemana) {
CASE SABADO:
System.out.println(“Actividad programada para el fin de semana”);
break;
CASE DOMINGO:
System.out.println(“Actividad programada para el fin de semana”);
break;
default:
System.out.println(“Actividad programada entre semana”);
break;
}
}
Public static void main (String [] args) {
AgendaActividades agenda =new AgendaActividades();
agenda.crearActividad (“Piano”,Dia.LUNES);
agenda.crearActividad (“Piscina”,Dia.MARTES);
agenda.crearActividad(“Ingles”,Dia.MIERCOLES);
}
Tal como se puede observar, el método crearActividad recibirá como parámetro la enumeración Día. Esta enumeración le llegará precargada desde el método main (Por ejemplo DIA.LUNES).
Uno de los elementos destacables del listado anterior, es la sentencia switch que permite evaluar de forma simple los tipos enumerados. Cuando se compila una enumeración, el compilador añade dos métodos estáticos a la clase:
valueOf (String): Retornará la constante que corresponda con la variable String pasada como parámetro.
values(): Retornará un array de tipo enum con las constantes de la enumeración.
Ejemplo
for (Dia diaSemana: Dia.values ()) {System.out.println(diaSemana);}
Información adicional de enumeraciones en el siguiente enlace: http://sowernal.com/1LLy
2. La clase Class
Las instancias de la clase Class representan a las clases e interfaces que se ejecutan en una aplicación Java.
Por ejemplo, una enumeración es un tipo de clase y una anotación es una especie de interfaz. Cada arreglo también pertenece a una clase que se refleja como un objeto de clase Class y que es compartida por todos los arrays con el mismo tipo de elemento y número de dimensiones.
Los tipos primitivos de Java (boolean, byte, char, short, int, long, float, y dobles), y la palabra clave void también están representados como objetos de la clase.
La clase Class no tiene ningún constructor público. En su lugar los objetos Class se construyen automáticamente gracias a la máquina virtual de Java como clases que son cargadas y llamadas por el método defineClass al cargar la clase.
La clase Class tiene una gran cantidad de métodos que permiten obtener diversa información sobre la clase de un objeto determinado en tiempo de ejecución.
En el siguiente link tenemos los métodos asociados a la Clase Class: http://sowernal.com/1LOX
Concepto de Reflection
En informática, reflexión (o reflexión computacional) es la capacidad que tiene un programa de ordenador para observar y opcionalmente modificar su estructura de alto nivel.
Java posee un API muy poderosa que no se encuentra en la gran mayoría de los lenguajes. Se trata del API Reflection, que permite comunicarse casi directamente con la máquina virtual de Java. Dicho de otra forma, permite inspeccionar y manipular clases y objetos sin conocer a priori con qué objetos
estamos trabajando. Este API es utilizado por la gran mayoría de los frameworks como por ejemplo Hibernate o Spring.
Podremos además, acceder a la información de los objetos, conociendo y/o ejecutando sus atributos y métodos públicos; y todo ello en tiempo de ejecución.
3. Casteo de Objetos
El casteo (casting) es un procedimiento para transformar una variable primitiva de un tipo a otro, o transformar un objeto de una clase a otra clase siempre y cuando haya una relación de herencia entre ambas. Existen distintos tipos de casteo (casting) de acuerdo a si se utilizan tipos de datos o clases.
Cabe resaltar que en toda conversión de datos de un tipo a otro se puede dar una pérdida de información.
Ejemplos:
//Ejemplo double convertido a int
int i= (int) 3.8;
En este caso el valor que se almacenará es la parte entera.
//Ejemplo long convertido a int
long largo= 9998887654321L;
int entero = (int)largo; //Trunca a un valor de 32 bits (longitud de
// datos enteros) que es igual a 20378923
Por tanto, es importante hacer con cuidado las asignaciones entre elementos de distinto tipo para evitar pérdida de información y trabajar, quizá sin notarlo, con datos incorrectos
La conversión explícita es útil cuando es necesario tratar, de manera temporal, un valor como si fuera de otro tipo.
Ejemplo, al dividir dos enteros si se desea obtener la división no entera, podría hacerse lo siguiente:
double resultado;
int total, nDatos
resultado = (double) total/ nDatos;
Si no se hubiera hecho la conversión explícita se habría realizado la división entera y no es lo deseado en este caso particular. Java admite la conversión de tipos con ciertas limitaciones. Consideremos una jerarquía de herencia como la que vemos en el siguiente diagrama de clases, sobre el que vamos a analizar las posibilidades de conversión de tipos de distintas formas.
Conversión hacia arriba
Se trataría por ejemplo de poner lo que está a un nivel inferior en un nivel superior, por ejemplo poner un profesor interino como profesor. Posible código: profesor43 = profesorinterino67;
Ponemos lo que está abajo (el profesor interino) arriba (como profesor). Esto es posible, pero dado que lo que está abajo generalmente contiene más campos y métodos que lo que está arriba, perderemos parte de la información. Sobre el objeto profesor43 ya no podremos invocar los métodos propios de los profesores interinos.
Conversión hacia abajo
Se trataría de poner lo que está arriba abajo, por ejemplo, poner un profesor como ProfesorInterino. Esto no siempre es posible. El supertipo admite cualquier forma (es polimórfico) de los subtipos: si el supertipo almacena el subtipo al que queremos realizar la conversión, será posible usando lo que se denomina “Enmascaramiento de tipos” o “Hacer casting” (cast significa “moldear”). Si el supertipo no almacena el subtipo al que queremos convertirlo, la operación no es posible y saltará un error.
Ejemplo:
Profesor p1; //p1 es tipo profesor. Admite ser profesor, ProfesorTitular o ProfesorInterino.
ProfesorInterino p44 = new ProfesorInterino(); //p44 es ProfesorInterino.
p1 = p44; // Conversión hacia arriba: sin problema. Ahora p1 que es tipo profesor, almacena un profesor interino
p44 = p1 // ERROR en la conversión hacia abajo. El compilador no llega tan lejos como para saber si p1 almacena un profesor interino u otro tipo de profesor y ante la incertidumbre salta un error. La forma de forzar al compilador a que “comprenda” que p1 está almacenando un profesor interino y por tanto puede asignarse a una variable que apunta a un profesor interino se llama “hacer casting”.
Escribiríamos lo siguiente: p44 = (ProfesorInterino) p1;
El nombre de un tipo entre paréntesis se llama “operador de enmascaramiento de tipos” y a la operación en sí la llamamos enmascaramiento o hacer casting. El casting es posible si el objeto padre contiene a un objeto hijo, pero si no es así aunque la compilación sea correcta en tiempo de ejecución saltará un error “ClassCastException”, o error al hacer cast con las clases. No siempre es fácil determinar a qué tipo de objeto apunta una variable. Por ello, el casting o enmascaramiento debiera evitarse en la medida de lo posible debido a que, si un programa compile, pero contenga potenciales errores en tiempo de ejecución es algo no deseable. Un programa bien estructurado normalmente no requerirá hacer casting reiteradamente, o lo hará de forma muy controlada. Si durante la escritura de un programa nos vemos en la necesidad de realizar casting, debemos plantearnos la posibilidad de reestructurar código para evitarlo. Cuando incluyamos conversiones de tipos usando casting en nuestro código, para evitar errores conviene filtrar las operaciones de enmascaramiento asegurándonos antes de hacerlas de que el objeto padre contiene un hijo del subtipo al que queremos hacer la conversión.
Conversión de lado a lado
Se trataría de poner lo que está a un lado al otro, por ejemplo, convertir un ProfesorInterino en ProfesorTitular o al revés. Esto no es posible en ningún caso.
Deteminación del tipo de variables con Instanceof
La palabra clave instanceof, todo en minúsculas, sirve para verificar el tipo de una variable. La sintaxis que emplearemos para instanceof y sus normas de uso serán las siguientes:
if (profesor43 instanceof ProfesorInterino) {…} else { …}
- Solo se pueden comparar instancias que se relacionen dentro de la jerarquía de tipos (en cualquier dirección) pero no objetos que no se relacionen en una jerarquía. Es decir, no podemos comparar profesores con taxis por ejemplo, porque no se relacionarán dentro de una jerarquía.
- Solo se puede usar instanceof asociado a un condicional. No se puede usar por ejemplo directamente en una impresión por pantalla con System.out.println(…).
Ejemplos de sintaxis:
if (profesor73 instanceof ProfesorInterino) --> la sintaxis es válida.
if (interino1 instanceof ProfesorInterino) --> la sintaxis es válida.
if (interino1 instanceof Profesor) --> la sintaxis es válida.
if (fecha1 instanceof Profesor) --> Error: las instancias no están en una
jerarquía de tipos
Ejemplo de uso instanceof.
El resultado de la ejecución sería algo similar:
***profesor73 es un objeto de tipo ProfesorInterino
profesor73 es un objeto de tipo Profesor ¡ES POLIMÓRFICO!
interino1 es un objeto de tipo Profesor ¡ES POLIMÓRFICO TAMBIÉN!
profesor1 no es un objeto de tipo ProfesorInterino. Nunca ha sido un interino.
Para este fin también podemos usar el método isAssignableForm() la diferencia con el anterior es que instanceof no puede ser usado con tipos primitivos y isAssignableFrom() puede ser usado para cualquier objeto y al recibir un valor null lanza una excepción.
isAssignableFrom() es parte de los métodos de la clase Class.
URLS de referencia: http://sowernal.com/1LOX
4. Operadores Lambda
Una expresión Lambda, representa una función anónima o la implementación anónima de una interfaz de un solo método. Con las expresiones Lambda representaremos, en una sola línea de código, la implementación de una interfaz funcional como si se tratase de una clase anónima. Esta nueva propiedad de Java 8, permitirá una simplificación importante de nuestro código.
La sintaxis base de las expresiones Lambda se puede dividir en tres partes: argumentos, al igual que cualquier método, se declaran los tipos y nombres de las variables de entrada.
Separador: su única función es separar los argumentos de entrada del cuerpo de la función.
Cuerpo: Será en el cuerpo de la función donde se evalúen los parámetros de entrada y se realice el retorno si fuese necesario.
Ejemplos de expresiones Lambda
Función que recibe dos números enteros y retorna el resultado de su suma.
(int x, int y) -> {return x +y ;}
Función que recibe un número entero y retorna una cadena de texto.
(int x) ->{return “Tu número es “ + x;}
Función que recibe una cadena de texto y retorna otra.
(String nombre) -> {return “Tu nombre es “ + nombre;}
Función que recibe una cadena de texto y no tiene ningún retorno.
(String nombre) -> {System.out.println (“Tu nombre es” + nombre) ;}
Función que no recibe parámetros y tampoco retorna ningún valor.
() -> {System.out.println (“Hola”)}
Ahora que la sintaxis está un poco más clara, desarrollaremos una calculadora capaz de realizar cualquier operación aritmética con dos números. Para ello, utilizaremos una interfaz funcional que representará esta funcionalidad:
public interface OperacionInterface {
public double operación (double x, double y);
}
Aquí se define que una operación aritmética se realizará sobre dos variables (x,y) retornando su resultado.
La clase Calculadora empleará esta interfaz.
public class Calculadora {
public static double realizaOperacion (double x, double y,
OperacionInterface op { return op.operacion(x,y)}
}
El método realizaOperacion recibirá como parámetro cualquier implementación de la interfaz funcional OperacionInterface. Con eso se consigue que la funcionalidad de nuestros métodos sea variable, dependiendo de cómo se haya implementado la interfaz.
Programa de prueba sin operadores Lambda.
Public class PruebaCalculadora {
Public static void main (String[] args) {
double x=6;
double y=5;
double resulSuma=Calculadora.realizaOperacion (x, y, new OperacionInterface()
{
public double operación (double x, double y) {
return x+y;
}
});
Double resulResta=Calculadora.realizaOperacion(x,y, new OpreacionInterface(){
Public double operación (double x, double y) {
return x-y;
}
});
System.out.println(“Resultado suma:” + resulSuma);
System.out.println(“Resultado suma:” + resulResta);
}
}
Puede observar la cantidad de código necesario para cada operación aritmética a realizar. Este tipo de programación mediante implementaciones anónimas es muy común en los programas Java, es por ello que en la última versión de la plataforma se intenta simplificar este modelo de programación mediante las expresiones Lambda.
A continuación, aplicaremos este tipo de expresiones al programa PruebaCalculadora y veremos cómo la cantidad de código será reducida notablemente.
public class PruebaCalculadora {
public static void main (String [] args) {
double x=6;
double y=5:
double resulSuma=Calculadora.realizaOperacion (x, y,(double a, double b) -
>{return a + b;});
Double resulResta=Calculadora.realizaOperacion (x, y,(double a, double b) -
>{return a - b;});
System.out.println (“Resultado suma:” + resulSuma);
System.out.println (“Resultado suma:” + resulResta);
}
}
Las expresiones Lambda se podrán reducir aún más en los siguientes casos: Los tipos de las variables de entrada podrán ser omitidos si estamos implementando una interfaz funcional.
(int x, int y) -> {return x +y;} se reduce a (x,y) -> {return x +y;}
La palabra reservada return podrá ser omitida junto con las llaves que delimitan el cuerpo de la función en los casos que exista una única línea de evaluación y retorno. Aunque el cuerpo de una función Lambda puede contener varias líneas, como normal general aplicaremos este tipo de expresiones para métodos cortos.
(int x, int y) -> {return x +y} se reduce a (x,y) -> (x + y)
Los paréntesis podrán ser omitidos cuando se trate una única variable de entrada y se haya omitido también su tipo.
(int x) -> {return x + 5} x->x +5
A continuación aplicaremos lo sugerido en el programa PruebaCalculadora:
public class PruebaCalculadora{
public static void main (String [] args) {
double x =6;
double y =5;
double resulSuma=Calculadora.realizaOperacion(x,y,(a,b)-> a+b);
double resulResta=Calculadora.realizaOperacion(x,y,(a,b)-> a-b);
System.out.println (“Resultado suma:” + resulSuma);
System.out.println (“Resultado suma:” + resulResta);
}
}
No hay comentarios:
Publicar un comentario