Curiosidad – Simple muy simple
Estaba revisando hoy algunos códigos de las preguntas del examen de certificación, tengo una asignatura en la Universidad que tiene relación con la preparación del examen de certificación en Java y me encontré con una de esas preguntas que tienen unas pocas líneas de código, pero que pueden hacerte dudar hasta de las cosas más básicas que creemos saber
.
Archivo Q37.java
package technicalblog;
/**
*
* @author Yasna Meza Hidalgo
*/
public class Q37 {
public static void main(String args[]){
int i = 0;
i = i---i---i;
System.out.println("1 : " + i);
}
}
La pregunta como siempre es qué es lo que genera en la salida estándar, pero como siempre digo que si estás pensando en rendir el examen entonces debes preguntarte POR QUÉ hace lo que hace … ahora antes de seguir leyendo deberías tratar de analizar y responder sin caer en la “divina” tentación de ayudarte del compilador
.
Si analizamos LA línea de código que asigna valor a la variable i tenemos que co-existen en la misma línea el operador unario (–) y el operador binario (-), ambos relacionados, evidentemente, con la sustracción de valores a variables numéricas. El operador unario (–) es uno de los primeros operadores curiosos que nos enseñan en los primeros cursos de programación, pero debemos tener cuidado con él para no sucumbir ante sus poderes ocultos.
Lo primero que hay que recordar es que el operador unario — puede ser de prefijo o sufijo y cuando está de sufijo primero se usa el valor y luego se decrementa en una unidad el valor de esa variable. Desde este punto de vista cuando se tiene que para: int x = 7; int y = x–; se tiene que el valor de la variable y es 7 y el valor de la variable x es 6; esto es porque primero se usa el valor de la variable x (en este caso para asignarla a la variable y) y luego se decrementa su valor.
OK, dada esta breve explicación sigamos con el análisis de la asignación y tenemos:
i = i—i—i; que se traduce a i = 0 – (-1) … el valor -1 viene dado por la disminución de 1 con el operador unario (–)
Luego si seguimos analizando la sentencia:
i=i—i—i lo que nos queda es 0 – (-1) – (-2) = 3, en este caso el -2 resulta del operador — que aparece como prefijo (a -1 que es el valor actual de i se le resta 1) .
Tal vez todo esto no logre generar confusión en la gran mayoría de las personas, pero por lo menos a mi me produce ciertas dudas al momento de evaluar, claramente tiene que ver con el orden de precedencia de los operadores y, obviamente, el saber cómo funciona el operador unario (–) cuando está como prefijo o sufijo.
Bueno, creo que eso es todo por ahora, nos leemos en el próximo post y gracias por leer
Bye
Tutorial básico Spring+JSF+Hibernate
Acá hay un ejemplo básico (más que el hola mundo eso sí
) que permite iniciarse con el uso de Spring en combinación con JSF e Hibernate. Trabaja con un modelo de datos de dos tablas y sólo contempla la implementación de un mantenedor básico de una de ellas. En una de las siguientes entregas estará el ejemplo en donde se incluya la navegación entre páginas y algo más de “producción” en la presentación.
Espero que les sirva de algo el Tutorial básico de Spring + Hibernate + JSF.
Bye
Patrones de diseño – Singleton
En la entrega anterior vimos el patrón de diseño Abstract Factory, en esta oportunidad voy a presentar un ejemplo del uso del patrón Singleton. Este patrón se encuentra dentro de la clasificación de los patrones creacionales.Muchas veces se requiere mantener en memoria sólo una instancia de una clase, por ejemplo: una conexión a una base de datos, un spool de impresión, entre otros ejemplos. Una solución sería mantener una variable lógica que sea seteada cuando se crea la primera instancia de la clase. El problema radica en que los constructores no devuelven valores explícitamente. Una posible solución sería tener un método que permita chequear el proceso de creación pudiendo retornar, por ejemplo, el valor de la variable lógica que indica el estado del proceso de creación, pero esto puede no resultar simplemente si olvidamos llamar al método que está chequeando el proceso de creación.
Una forma un poco más elegante es a través del uso del patrón de diseño Singleton
Vamos a partir creando la clase SingletonException extendiendo de RuntimeException
Archivo SingletonException.java
package ejemplosingleton;
/** * * @author Yasna Meza Hidalgo */
public class SingletonException extends RuntimeException{
public SingletonException(){ super(); }
public SingletonException(String s){ super(s); }
}
La clase anterior nos va a permitir manejar el “error” de intentar crear un spooler de impresión cuando ya ha sido creado.Luego creamos la clase que va a implementar el patrón de diseño.
Archivo SpoolImpresion.java
package ejemplosingleton;
/** * * @author Yasna Meza Hidalgo */
public class SpoolImpresion {
static boolean instancia = false;
public SpoolImpresion() throws SingletonException{
if (instancia) throw new SingletonException("Sólo un spooler permitido");
else instancia = true;
}
public void finalizar(){ instancia = false; }
}
Ahora veremos cómo usar esta clase
. La idea es que se va a mantener un sólo Spool de impresión, quizás se podría pensar en comenzar a agregar trabajos de impresión, pero no es el objetivo del ejemplo, así es que desechada la idea.
Vamos a intentar crear una instancia de la clase, que debería detectar si es que existe algo ya creado y en el caso de que así sea me debería advertir para no tener dos instancias de la misma clase.
SpoolImpresion spx = null, spy = null;
System.out.println("Un spooler de impresión");
try{
spx = new SpoolImpresion();
System.out.println("Spooler creado con éxito");
}
catch(SingletonException se){ System.out.println("Mensaje : " + se.getMessage()); }
/* Intenta abrir un segundo spooler de impresión */
System.out.println("Intenta crear otro spooler de impresión");
try{
spy = new SpoolImpresion();
System.out.println("Spooler creado con éxito");
}
catch(SingletonException se){ System.out.println("Mensaje : " + se.getMessage()); }
Al intentar ejecutar el código anterior se produce la siguiente salida:
Un spooler de impresión
Spooler creado con éxito
Intenta crear otro spooler de impresión
Mensaje : Sólo un spooler permitido
Las dos primeras líneas son producto de intentar crear la primera instancia, acá no hay problema porque no hay instancias.
Luego, cuando intentamos crear una segunda instancia se genera la excepción que nos indica que NO SE PUEDE.
Lo anterior tiene sentido porque la instancia ya existe y NO SE PERMITE TENER MÁS DE UNA. Ahora si intentamos probar con el siguiente trozo de código:
System.out.println("Otro spooler de impresión ... podré ahora");
try{
spx.finalizar();
spy = new SpoolImpresion();
System.out.println("Spooler creado con éxito");
}
catch(NullPointerException se){ System.out.println("Mensaje : " + se.getMessage());}
catch(SingletonException se){ System.out.println("Mensaje : " + se.getMessage()); }
La salida en este caso será:
Otro spooler de impresión … podré ahora
Spooler creado con éxito
Dado que se ha finalizado (sería como devolver el recurso) el spooler que estaba creado; al intentar crear uno nuevo se crea y retorna con éxito
. El ejemplo es bastante simple y se le pueden agregar varias otros adornos, pero por ahora sólo interesaba mostrar como mantener una sola instancia de una clase usando el patrón de diseño Singleton.
Esperando haber sembrado la inquietud de trabajar en estos ámbitos, me despido, será hasta la próxima entrega
Bye
Patrones de diseño en Java
A continuación veremos la aplicación de patrones de diseño en Java, normalmente se puede leer mucho al respecto, pero lo que realmente interesa es saber cuándo utilizarlos y cómo se terminan implementando esos conceptos.
En esta entrega revisaré los conceptos generales de patrones de diseño, indicando la utilidad que presentan y un ejemplo de implementación de los mismos.
La idea central que hay en los patrones de diseño es la estandarización de la información acerca de un problema común y su solución. Existen varios tipos de patrones:
- Creación, patrones para crear objetos
- Comportamiento, patrones para coordinar la interacción funcional entre objetos
- Estructural, patrones para manejar relaciones estáticas y estructurales entre objetos
- Sistema, patrones para manejar la interacción a nivel de sistema
También es posible asignarle un nivel al patrón y según la literatura se tienen los siguientes niveles:
- Clase simple, el patrón se aplica a una sola clase
- Componente, el patrón se aplica a un grupo de clases
- Arquitectura , el patrón es usado para coordinar acciones de sistemas y sub-sistemas.
Ahora, vamos a revisar algunos ejemplos de patrones de diseño
Patrones de creación. Este tipo de patrones soporta una de las tareas más comunes en la programación orientada a objetos, la creación de objetos en un sistema. Algunos patrones de este tipo son:
- Abstract factory
- Builder
- Factory Method
- Prototype
- Singleton
De la lista anterior los patrones Abstract Factory y Factory method están basados en el concepto de creación de objetos flexible.
EJEMPLO DE ABSTRACT FACTORY
Tipo de patrón: creación; Nivel: componente; Propósito: entregar un contrato para crear familias de objetos u objetos dependientes sin tener que tener una clase especifica.
Contexto del ejemplo. Implementar una aplicación que permita manejar información relacionada con direcciones y números telefónicos.
Una posible solución pudiera ser definir clases que permitan representar las direcciones y los números telefónicos y la implementación de estas clases puede contener la aplicación de las reglas de negocio que se pretendan establecer, como por ejemplo, la cantidad de dígitos con los que deben contar los números de teléfonos en cada localidad del mundo, el formato de los códigos postales, entre otros detalles. Si la información que se pretende manejar es de unos pocos países no hay mayor problema, pero qué pasa si se comienzan a incluir muchos países, que tengan reglas distintas de composición de sus números telefónicos, vamos a crear una clase que represente a cada país y allí implementaremos las reglas aplicadas a ese país, lo mejor que nos podría pasar es que al agregar un nuevo país se tenga que recompilar todo el código para agregar las reglas que ha establecido el nuevo país que deseamos agregar, eso NO ES FLEXIBILIDAD.
En este caso es posible aplicar este tipo de patrón de diseño, así como también en las siguientes situaciones:
- El cliente debe ser independiente de la forma en la que los productos son creados
- La aplicación debe ser configurada para una de múltiples familias de productos
- Los objetos necesitan ser creados como un conjunto en pro de que sean compatibles
Es posible definir un conjunto de clases que permitan definir las relaciones y contratos, sin necesidad de definir implementaciones.
El diagrama de clases que sustenta a este patrón de diseño es el que se indica en la siguiente figura:
La descripción de diagrama:
- AbstractFactory. Declara una interfaz con operaciones para crear productos abstractos
- ConcreteFactory. Implementa las operaciones para crear objetos-productos concretos
- AbstractProduct. Declara una interfaz para un tipo de objeto-producto
- Product. Define un objeto-producto a ser creado con la correspondiente ConcreteFactory, implementa la interfaz AbstractProduct
Vamos ahora a la implementación, en nuestro caso las clases equivalentes del modelo general son:
- AbstractFactory. AddressFactory
- ConcreteFactory. ChileAddressFactory
- AbstractProduct. Address, NumberPhone
- Product. ChileAddress, ChileNumberPhone
Archivo: Address.java
package ejemplopd;
/** * * @author Yasna Meza Hidalgo */
public abstract class Address {
public String getCalle() { return calle; }
public String getCiudad() { return ciudad; }
public String getCodigoPostal() { return codigoPostal; }
public String getRegion() { return region; }
public void setCalle(String calle) { this.calle = calle;}
public void setCiudad(String ciudad) { this.ciudad = ciudad; }
public void setCodigoPostal(String codigoPostal) { this.codigoPostal = codigoPostal; }
public void setRegion(String region) { this.region = region; }
private String calle;
private String ciudad;
private String region;
private String codigoPostal;
}
Archivo AddressFactory.java
package ejemplopd;
/** * * @author Yasna Meza Hidalgo */
public interface AddressFactory {
public Address crearDireccion();
public NumberPhone crearTelefono();
}
Archivo ChileAddress.java
package ejemplopd;
/** * * @author Yasna Meza Hidalgo */
public class ChileAddress extends Address{
private static final String PAIS = "CHILE";
public String getPais() { return PAIS; }
public String getDireccion() { return this.getCalle() + " " + this.getCiudad() + " " + this.getRegion() + " " + this.getCodigoPostal() + " " + PAIS; }
@Override
public String toString() { return getDireccion();}
}
Archivo ChileAddressFactory.java
package ejemplopd;
/** * * @author Yasna Meza Hidalgo */
public class ChileAddressFactory implements AddressFactory{
public ChileAddress crearDireccion(){ return new ChileAddress(); }
public ChileNumberPhone crearTelefono(){ return new ChileNumberPhone(); }
}
Archivo NumberPhone.java
package ejemplopd;
/** * * @author Yasna Meza Hidalgo */
public abstract class NumberPhone {
public String getNumero() { return numero; }
public void setNumero(String numero) {
try{
Long.parseLong(numero);
this.numero = numero;
} catch(NumberFormatException error){}
}
private String numero;
public abstract String getCodigoPais();}
Archivo ChileNumberPhone.java
package ejemplopd;
/** * * @author Yasna Meza Hidalgo */
public class ChileNumberPhone extends NumberPhone {
private static final String CODIGO_PAIS = "56";
private static final int LONGITUD_NUMERO = 7;
public String getCodigoPais() { return CODIGO_PAIS; }
public String getCodigoCiudad() { return codigoCiudad; }
public void setCodigoCiudad(String codigoCiudad) { this.codigoCiudad = codigoCiudad; }
private String codigoCiudad;
@Override
public String toString(){ return CODIGO_PAIS + "-" + this.getCodigoCiudad() + "-" + this.getNumero(); }
@Override
public void setNumero(String numero){
if (numero.length() == LONGITUD_NUMERO) super.setNumero(numero);
}
}
Las clases e interfaces anteriores implementan el modelo asociado al patrón de diseño, ahora la pregunta es cómo se van a crear objetos de las clases concretas o_O, la respuesta está en las siguientes líneas de código:
package ejemplopd;
/**
*
* @author Yasna Meza Hidalgo
*/
public class Main {
/**
* @param args the command line arguments
*/
public static void main(String[] args) {
/* Vamos a crear objetos para mostrar el uso del patrón de diseño */
ChileAddress nueva = new ChileAddressFactory().crearDireccion();
ChileNumberPhone nuevo = new ChileAddressFactory().crearTelefono();
/* Setea los datos de la dirección */
nueva.setCalle("Anibal Pinto");
nueva.setCiudad("Concepcion");
nueva.setCodigoPostal("430");
nueva.setRegion("Octava");
/* Setea los datos del número de telefono */
nuevo.setNumero("2234567");
nuevo.setCodigoCiudad("41");
/* Imprime los objetos en la entrada estándar */
System.out.println(nueva);
System.out.println(nuevo);
}
}
En las líneas 14 y 15 se usa la “fábrica” de objetos para crear una dirección y un número de teléfono, luego se “setean” los valores de cada objeto y finalmente se imprimen (como objetos) en la salida estándar. Está claro que el hecho de poder manejar direcciones y números de teléfono se puede manejar sin necesidad de usar patrones de diseño, simplemente manteniendo un par de clases (comunes y corrientes) evitando trabajar con lo que, para algunos, son “clases innecesarias”; sin embargo, siempre van a existir muchas formas de hacer algo, acá estoy mostrando la aplicación del concepto de uno de los patrones de diseño, sin ánimo de decir que es la única y mejor forma de resolver el problema.
Si se quisiera agregar un nuevo tipo de dirección o un número de teléfono, basta con agregar la clase y extenderla de las clases abstractas y en ellas se pueden implementar, con toda libertad, las reglas de negocio asociadas a la nueva dirección y/o número de teléfono.
La próxima entrega estará relacionada con algún otro patrón, quizás de mayor “peso” para ir avanzando en el tema.
Bye
Ocultamiento de atributos
Uno de los tópicos que más se analiza cuando estamos preparando el examen de certificación SCJP tiene que ver con el “hide” de los atributos junto con la sobreescritura de los métodos y es uno de los temas que más confusión puede generar. Como una forma de intentar aclarar esto revisemos el siguiente ejemplo:
package technicalblog;
/**
*
* @author Yasna Meza Hidalgo
*/
public class B {
public static void main(String ... args){
C cx = new D(2);
D dx = new D(3);
C ccx = new C(6);
System.out.println("x de cx : " + cx.x);
System.out.println("x de dx : " + dx.x);
System.out.println("x de ccx : " + ccx.x);
System.out.println("getX() de cx : " + cx.getX());
System.out.println("getX() de dx : " + dx.getX());
System.out.println("getX() de ccx : " + ccx.getX());
}
}
class C{
public int x;
C(){}
C(int x){
this.x = x;
}
public int getX(){ return this.x; }
}
class D extends C{
public int x;
D(int x){
this.x = x;
}
@Override
public int getX(){ return this.x + 2; }
}
Lo importante a destacar acá es cómo se decide qué versión del método se va a escoger, qué es lo que lo determina la forma en que creaste el objeto o la forma en la cual está declarado, se tiene el mismo comportamiento con los atributos?.
Si analizamos el resultado al intentar ejecutar el código anterior tenemos:
x de cx : 0
x de dx : 3
x de ccx : 6
getX() de cx : 4
getX() de dx : 5
getX() de ccx : 6
En este caso estamos creando objetos considerando todas las posibilidades que se pueden tener. Primero en el caso del objeto cx este está declarado del tipo C, pero contiene una referencia de D (la sub-clase); en este caso cuando se intenta acceder al atributo x considera la forma en la que está declarado y, por ende, comienza a buscar desde esa clase y es a ese atributo x (el que se encuentra en la clase C) al cual se hace referencia; sin embargo cuando se trata de acceder al método getX() se decide dependiendo de la forma en que fue creado el objeto, es por esa razón que se ejecuta la versión del getX() que se encuentra en la clase D e imprime 4 (resultado de sumar 2, el valor asignado al atributo, más el valor constante 2). Ahora si analizamos el caso del objeto dx este fue creado y declarado de la misma forma, por lo tanto se justifica que imprima 3 (valor del atributo x que se encuentra en la clase D) y al intentar llamar al método getX() ejecuta la versión de D, por esa razón imprime 5 (3 + 2). Finalmente, y para terminar de aclarar el tema se crea una instancia de la clase C, pero asignándola a un objeto del mismo tipo … entonces, deberíamos esperar que imprima 6 (valor del atributo x que puede ver) y 6 como valor de retorno de la versión de getX() que tiene.
En resumen, la selección de los atributos se hace de acuerdo al tipo que se ha usado en la declaración y la selección del método se hace de acuerdo al tipo de referencia que está conteniendo el objeto.
Esperando haber aclarado este recurrente tema de las preguntas de certificación, me despido hasta el próximo post.
Bye
Clases dentro de clases …
Hace unos días en una de mis clases del electivo de SCJP que dicto en la Universidad uno de mis estudiantes me preguntó acerca de la posible definición de clases dentro de clases, gracias a eso recorde lo que me sorprendió a mi cuando averigue lo que eso significaba
. Así es que trataremos de explicar en qué consiste esto, con un ejemplo relativamente simple:
Archivo EjemploInnerClass.java
/**
*
* @author Yasna Meza Hidalgo
*/
public class ExampleInnerClass {
public static void main(String ... args){
A a = new A();
System.out.println("This is C!");
}
}
class A{
A() { System.out.println("This is A!"); }
{ new B(); }
class B{
B() { System.out.println("This is B!"); }
}
}
La pregunta de rigor acá es: qué es lo que se imprime? y esto implica lo que tiene que ver con los bloques de inicialización que puede contener una clase (tema que fue abordado en profundidad en uno de los post anteriores) … analizando el ejemplo anterior se tiene que lo primero que ejecuta es la creación del objeto usando la clase A, eso hace que ejecute el bloque de inicialización antes del constructor y desde ahí se crea un objeto anónimo de la clase B (usando el constructor sin argumentos) y eso hace que la primera línea que aparece en la salida estándar sea “This is B!”, luego de eso se ejecuta el constructor sin argumentos de A lo que justifica que se imprima “This is A!” y finalmente, cuando se regresa el control al método main se imprime “This is C!”.
Ahora bien, qué sucede si se agrega un atributo a la clase “anidada” (en este caso la clase B), cómo se podrá acceder a ese atributo desde la clase A o desde un objeto de la clase A. Para explicar lo anterior, revisemos el siguiente ejemplo:
Archivo A.java
package technicalblog;
/**
*
* @author Yasna Meza Hidalgo
*/
public class A {
class AA{
private float aa;
public int bb;
AA(float aa){ this.aa = aa; }
float getAA(){ return this.aa; }
}
A(){
class B{
{ System.out.println("I am getting loaded"); }
}
new B();
}
A(int i){
System.out.println("No B this time");
}
}
Qué sucede si se intenta ejecutar:
A.AA x = new A().new AA(29.6f);
System.out.println(x.getAA());
A y = new A();
En el ejemplo anterior se puede apreciar la forma en la cual se pueden acceder a las clases anidadas, en este caso es estrictamente necesario crear un objeto de la clase A para poder tener la posibilidad de crear un objeto de la clase AA (anidada). En este caso el objeto “x” es que que representa a un objeto de la clase AA y desde x se puede acceder (como si fuera un objeto común y corriente) a todos los elementos que pertenezcan a la clase AA y el código anterior debería mostrar:
I am getting loaded
29.6
I am getting loaded
La verdad es que este tema da para bastante más, pero para comenzar sólo quise describir lo más esencial y luego se pueden ir aplicando conceptos más avanzados, tema para otro post
.
Bueno, creo que eso es todo por ahora, les agradezco que hayan leído hasta este punto y nos leemos luego.
Bye
Métodos virtuales en C#
Vamos a revisar el concepto de métodos virtuales en C#, una manera distinta de enfrentar el concepto de método abstracto.
Consideremos la siguiente clase
Archivo A.cs
using System;
using System.Collections.Generic;
using System.Text;
namespace MetodosVirtuales
{
class A
{
public virtual void m() {}
public virtual void n() {}
public void o() { Console.WriteLine("Método o desde A"); }
}
}
En esta clase tenemos dos métodos virtuales que no tienen implementación y si se intentan ejecutar NO HABRÁ salida alguna, puesto que no hay implementación
. Extendemos ahora nuestro esquema y agregamos la clase B:
Archivo B.cs
using System;
using System.Collections.Generic;
using System.Text;
namespace MetodosVirtuales
{
class B : A
{
public override void m()
{
base.m();
Console.WriteLine("Método m desde B");
}
new public void n()
{
Console.WriteLine("Método n desde B");
}
new public void o()
{
Console.WriteLine("Método o desde B");
}
}
}
En esta clase se está redefiniendo el método m (considerando la versión que existe en la super clase), se define un nuevo método n y un nuevo método o.
Si agregamos la clase C y D al esquema:
Archivo C.cs
using System;
using System.Collections.Generic;
using System.Text;
namespace MetodosVirtuales
{
class C : A
{
new public virtual void m() { }
}
}
Archivo D.cs
using System;
using System.Collections.Generic;
using System.Text;
namespace MetodosVirtuales
{
class D : C
{
public override void m()
{
Console.WriteLine("Método m desde D");
}
}
}
En este caso la clase C define un nuevo método virtual y la clase D sobre-escribe (redefine) el método m de la clase C. El diagrama de clases queda entonces como se indica en la siguiente figura:

Revisemos ahora el comportamiento de este esquema cuando se comienzan a crear objetos de las clases que pertenecen al esquema y de la “selección” de los métodos que se van a ejecutar:
Archivo Program.cs
using System;
using System.Collections.Generic;
using System.Text;
namespace MetodosVirtuales
{
class Program
{
static void Main(string[] args)
{
A z;
A x = new B();
D y = new D();
/* Muestra el uso de los métodos virtuales */
Console.Write("Salida 1 ");
x.m(); // Llama al método m de B
Console.Write("Salida 2 ");
y.m(); // Llama al método m de D
Console.Write("Salida 3 ");
x.n(); // LLama al método n de B
Console.Write("Salida 4 ");
y.n();
Console.Write("Salida 5 ");
x.o(); // Llama al método o de B
z = x;
Console.Write("Salida 6 ");
z.o(); // Llama al método o de A
Console.Write("Salida 7 ");
z.m();
Console.ReadKey();
}
}
}
Analicemos ahora las salidas para tratar de explicar la “selección” de los métodos. En la línea 17 se genera la salida 1 y se imprime “Método m desde B“; esta salida se justifica dado que el objeto que está llamando al método es una instancia de la clase B, por lo que la versión del método que se escoge es el que se encuentra en la clase B, esto es independiente de que el objeto esté declarado a nivel de la clase A. Para el caso de la salida 2 el objeto está declarado y creado usando la clase D, por lo que se ejecuta la versión del método m que se encuentra en la clase D. Para el caso de la salida 3 y salida 4 no se tiene nada que imprimir, se ven las versiones de n de la clase A (de forma transtitiva); acá es super válido preguntarse qué versión de n se ejecutará usando las siguientes líneas:
B w = new B();
w.n();
En este caso se va a considerar el método n que se encuentra en la clase B (es una nueva versión, no se considera la versión de la clase padre) y se va a imprimir “método n desde B”
En la línea # 25 se está llamando al método o de la clase A dado que el objeto está declarado de la clase A por lo que desde ahí se escoge la versión de esa clase, nuevamente cabe preguntarse qué sucede si se ejecutan las siguientes líneas:
B a = new B();
a.o();
En este caso se ejecuta la versión del método o que se encuentra en B (que es una versión nueva y no redefinida).
Finalmente, en la línea 26 en adelante se demuestra el comportamiento de los objetos cuando reciben la referencia y no son creados “directamente” a través de un constructor. En este caso el objeto z se encuentra declarado a nivel de A (la clase más general de la jerarquía) y recibe un objeto que contiene un objeto de la clase B (declarado al mismo nivel de z) y ejecuta la versión del método o que se encuentra en A; en caso de que se intente ejecutar:
B w = new B();
z = w;
Se vuelve a ejecutar el método o de la clase A, dado que z está declarado a ese nivel.
Este tema de la selección del método que se va a ejecutar siempre tiene un componente de sorpresa dependiendo del lenguaje que se utilice, en el próximo post dejaré una versión de este mismo problema, pero en Java para poder ver la diferencia de los lenguajes en ese sentido (si es que existe alguna).
Espero que esto sirva para entender el funcionamiento de los métodos virtuales en C# o al menos para sembrar la inquietud respecto del tema.
Bye
Trabajando con tipos de datos en C y Java
Repasando algunos temas relacionados con los tipos de datos primitivos en C estabamos resolviendo ejercicios que tienen relación con el siguiente ejemplo:
Archivo Recuerdos.c
#include <stdio.h>
double f(int, int);
int g(int, int);
double fx(double, int);
int gx(double, int);
int main(){
fprintf(stdout, "Output 1 : double f(int, int) : %lf\n", f(2,3));
fprintf(stdout, "Output 2 : double f(int, int) : %lf\n", f(2.0,3));
fprintf(stdout, "Output 3 : int g(int, int) : %d\n", g(2,3));
fprintf(stdout, "Output 4 : double fx(double, int) : %lf\n", fx(2.9,3));
fprintf(stdout, "Output 5 : int fx(double, int) : %d\n", gx(2.9,3));
return 0;
}
double f(int x, int y){ return x/y; }
int g(int x, int y){ return x/y; }
double fx(double x, int y){ return x/y; }
int gx(double x, int y){ return x/y; }
Una pregunta válida en este ejemplo es por qué razón la llamada a la función f(2,3) (Output 1) imprime 0.000000? y si llamamos a f(2.0, 3) imprime, nuevamente, 0.00000?
La respuesta a estas preguntas pasa por la característca de “cierre” de las operaciones matemáticas y la compatibilidad de los tipos de datos primitivos de los lenguajes, lo que en cierta forma tiene relación con la característica que tienen algunos lenguajes de programación que califica a un lenguaje como fuertemente tipado. C no es un lenguaje fuertemente tipado, dado que el programa anterior NO ARROJA error en tiempo de compilación; sin embargo, si se intenta implementar la misma idea en Java se tiene el siguiente código:
Archivo EjemploTDP.java versión 1.0
public class EjemploTDP {
public static void main(String ... a) {
System.out.println("Output 1 : int f(int, int) : " + f(2,3));
System.out.println("Output 2 : int f(double, int) : " + f(2.0,3));
System.out.println("Output 3 : double g(int, int) : " + g(2,3));
System.out.println("Output 4 : double g(double, int) : " + g(2.0,3));
}
public static int f(int x, int y){ return x/y; }
public static int f(double x, int y){ return x/y; }
public static double g(int x, int y){ return x/y; }
public static double g(double x, int y){ return x/y; }
}
La clase anterior NO COMPILA ya que el compilador chequea los tipos de datos en tiempo de ejecución, esto es lo le da a Java la característica de ser fuertemente tipado y se debe hacer el siguiente cambio para que la clase pueda superar la fase de compilación:
Archivo EjemploTDP versión 2.0 (compilando)
public class EjemploTDP {
public static void main(String ... a) {
System.out.println("Output 1 : int f(int, int) : " + f(2,3));
System.out.println("Output 2 : int f(double, int) : " + f(2.0,3));
System.out.println("Output 3 : double g(int, int) : " + g(2,3));
System.out.println("Output 4 : double g(double, int) : " + g(2.0,3));
}
public static int f(int x, int y){ return x/y; }
public static int f(double x, int y){ return (int) x/y; }
public static double g(int x, int y){ return x/y; }
public static double g(double x, int y){ return x/y; }
}
Ahora si todo va bien con el proceso de compilación.
Continuando con el tema veremos ahora porque razón en ambos lenguajes cuando se intenta dividir dos números enteros y se intenta retornar el resultado como un double resulta un 0.0?
Esto es porque las operaciones son cerradas y cuando se tienen dos números enteros y se aplica el operador de división sobre ellos el resultado será un número entero, sin importar que el tipo de dato a retornar es un double. En estos casos se debe aplicar la llamada conversión explícita y, en el caso de C se tiene el siguiente cambio:
double f(int x, int y){ return (double) x/y; }
En el caso de Java se debe cambiar el método por:
public static double g(int x, int y){ return (double) x/y; }
De esta forma se tiene el resultado esperado, es decir, que imprima el resultado considerando todos los decimales. Esto casi siempre es un tema en los lenguajes de programación, lo ideal es que las conversiones “no tradicionales” entre tipos de datos sean detectados en tiempos de compilación (ahi los errores son más baratos jejeje).
Bueno, creo que eso es todo por ahora … este post no fue una comparación de lenguajes para ver cuál es mejor o peor, sino que simplemente ver una de las características que hace la diferencia entre uno y otro.
Hasta el próximo post.
Bye
Ejemplo básico en JavaFX
Hace un tiempo, me pidieron en un foro en el que participaba un ejemplo de cómo poder conectar una clase (común y corriente en Java) con una aplicación en JavaFX y esto fue lo que envíe:
Archivo Main.fx
/**
* Main.fx
*
* Created on 11-02-2010, 11:08:19 AM
*/
package exampleone;
import javafx.stage.Stage;
import javafx.scene.Scene;
import javafx.scene.text.Text;
import javafx.scene.text.Font;
import classes.Human;
/**
* @author Yasna Meza Hidalgo
*/
var currentHuman:Human = null;
currentHuman = new Human("Alfa");
Stage {
title: "Example JavaFX - Use class"
width: 250
height: 80
scene: Scene {
content: [
Text {
font : Font {
size : 16
}
x: 10
y: 30
content: currentHuman.getName()
}
]
}
}
Archivo Human.java
package classes;
/**
*
* @author Yasna Meza Hidalgo
*/
public class Human {
private String name;
public Human(String name){
this.name = name;
}
/**
* @return the name
*/
public String getName() {
return name;
}
/**
* @param name the name to set
*/
public void setName(String name) {
this.name = name;
}
}
Sirvio de algo en esa oportunidad, así es que lo replico acá ahora.
Bye
Conversiones en C
El tema de las conversiones (explícitas o implícitas) en los lenguajes de programación siempre es un tema, sobre todo cuando tratamos de saber si algunas cosas pueden ser válidas o no. A continuación se tiene un programa en C que permite realizar conversiones entre los tipos de datos primitivos (int y char).
/*
@Buscando el comportamiento de las conversiones entre int y char
@versión. Yasna Meza Hidalgo - 2010
*/
#include <stdio.h>
int main(){
char c = '0';
int x;
/* La conversión más simple */
x = c;
fprintf(stdout, "%d\n", c);
fprintf(stdout, "%c\n", c);
fprintf(stdout, "%d\n", x);
/* Algo un poco más elaborado */
c = '0' + '1';
x = c;
fprintf(stdout, "%c\n", c);
fprintf(stdout, "%d\n", x);
/* Algo un poco mucho más elaborado */
c = '0' + 1;
x = c;
fprintf(stdout, "%c\n", c);
fprintf(stdout, "%d\n", x);
return 0;
}
En este caso se tienen tres casos de posibles conversiones, partiendo de la más simple hasta llegar a una donde hay operaciones involucradas. Primero, se tiene la asignación de x = c; esto se entiende como una conversión implícita x va a guardar el código ASCII del caracter almacenado en c (esto es un 48), luego se intenta imprimir el valor de c como un char (usando el %c) y se imprime un 0 (es el caracter y no el número) y después se imprime como int (usando %d) esto es el código ASCII de lo almacenado en c.
Luego se realiza la siguiente asignación: c = ’0′ + ’1′; lo que se debe interpretar como la suma (aritmética) de los códigos ASCII de los caracteres 0 y 1 ( 48 + 49 = 97).
Finalmente, se realiza la asignación: c = ’0′ + 1; lo que se debe interpretar como la suma de una unidad al código ASCII del caracter 0 lo que resulta en un 49 (48 + 1); por lo tanto, cuando se imprime c como caracter se imprime un 1 (símbolo asociado al 49).
Bueno, eso sería por ahora … espero haber aclarado un poco este tema y quizás plantear la inquietud de seguir indagando en este tema de las conversiones entre tipos de datos.
Bye
-
Archivos
- octubre 2011 (1)
- julio 2011 (2)
- marzo 2011 (1)
- octubre 2010 (1)
- septiembre 2010 (3)
- agosto 2010 (10)
-
Categorías
-
RSS
RSS de las entradas
RSS de los Comentarios
