[Las cosas que no interesan]
lunes jun 25, 2007
SCJP for JSE5 - 9
SOBREESCRITURA Y SOBRECARGA
1. Sobreescritura de métodos.
La sobreescritura de métodos consiste en la habilidad de definir cierto tipo de comportamiento que es específico para una subclase. Por ejemplo:
class Humano{
public void caminar(){
//caminar como un ser humano
}
//otros métodos de humanos...
}
class Pepe extends Humano{
public void caminar(){
//caminar de forma particular como Pepe
}
}
En la implementación anterior la clase Humano define un grupo de métodos que aplicará a todas sus subclases. Pepe por ejemplo, hereda todos los métodos heredables de Humano, pero por alguna razón, define un comportamiento específico para caminar. Quizá por que Pepe sea cojo, tenga un pie más largo que otro o por que camina muy rápido, en fin, de esto se trata la sobreescritura de métodos: las subclases pueden aprovechar todos los métodos que heredan de sus padres y además personalizar el comportamiento que no se acomode a sus necesidades.
No se debe confundir la sobreescritura de métodos con la implementación. Cuando se heredan métodos abstractos de una clase (abstracta por supuesto), estos se implementan no se sobreescriben.
Las reglas para sobreescribir métodos son:
- La lista de argumentos debe ser exactamente igual a la del método sobreescrito. Si esta regla no se cumple, probablemente se obtiene un método sobrecargado.
- El tipo de retorno debe ser el mismo o un subtipo del tipo de retorno declarado en el método sobreescrito en la superclase. Lo de subtipo es nuevo en JSE5, se le llama "Covariant Returns" y se verá más adelante.
- El nivel de acceso no puede ser más restrictivo que el del método sobreescrito.
- Los métodos marcados como private, final o static no pueden ser sobreescritos.
- El método que sobreescribe no puede lanzar 'Checked Exceptions' nuevas o de nivel superior a las declaradas en el método sobreescrito.
- El método que sobreescribe no tiene que declarar excepciones que no vaya a lanzar, no importa las que hayan declaradas en el método original.
OJO: Si un método no puede ser heredado tampoco puede ser sobreescrito.
Invocar la versión de la superclase de un método sobreescrito
Sobreescribir un método no quiere decir que el método de la clase de nivel superior ya nunca más puede ser invocado con una instancia de la subclase; la palabra reservada super hace referencia a la instancia de la superclase (sí, al crear la instancia de una subclase, también se crea una de la superclase) y con esta palabra podemos invocar los métodos que han sido sobreescritos en la subclase. Por ejemplo:
class Humano{
public void caminar(){
//caminar como un ser humano
}
}
class Pepe extends Humano{
public void caminar(){
super.caminar(); //invoco la forma de caminar de la clase superior
//implemento las particularidades del caminado de Pepe
}
}
El uso de la palabra super para invocar métodos sobreescritos, está restringido para los métodos de instancia que sean accesibles por la subclase (los no private).
2. Sobrecarga de métodos
- La sobrecarga de métodos permite reutilizar el mismo nombre de un método en una clase pero con una lista de argumentos diferente y se puede cambiar el tipo de retorno. Los métodos se pueden sobrecargar en la misma clase o una subclase puede sobrecargar uno o varios métodos de la clase padre. Un ejemplo:
class Humano{
public void caminar(int kilometros){
//caminar los metros indicados
}
public void caminar(Recorrido rr){
//seguir el recorrido pasado como parámetro
}
}
class Pepe{
public void caminar(Direccion dir){
//caminar hacia la dirección indicada
}
}
Note que en el ejemplo anterior el método caminar() de la clase Pepe no sobreescribe ninguno de Humano sino que lo sobrecarga. Así una instancia de la clase Humano tiene acceso a dos formas del método caminar: una que recibe el número de kilómetros y otra que recibe el recorrido a hacer; mientras una instancia de la clase Pepe tiene acceso a tres formas del método caminar: dos heredadas de Humano y otra sobrecargada en Pepe que recibe una dirección.
Las reglas para sobrecargar métodos son:
- Los métodos sobrecargados tienen que cambiar la lista de argumentos.
- Un método sobrecargado puede cambiar el tipo de retorno.
- Un método sobrecargado puede cambiar el modificador de acceso.
- Un método sobrecargado puede declarar excepciones nuevas o de nivel superior.
Dado el método public Integer calcularPrecioProducto(int valorCompra, double ganancia) throws Exception, algunas versiones sobrecargadas de este método pueden ser:
protected Double calcularPrecioProducto(int valorCompra, double ganancia)
public Integer calcularPrecioProducto(int valorCompra)
public Integer calcularPrecioProducto(double ganancia) throws SQLException
- La decisión de la versión del método que se va a invocar está basada en la lista de argumentos y se hace en tiempo de compilación. Por ejemplo al compilar y ejecutar la clase EjemploSobrecarga:
class Humano{}
class Pepe extends Humano{}
class EjemploSobrecarga{
void hacerAlgo(Humano hum){
System.out.println("Procesando un Humano...");
}
void hacerAlgo(Pepe pe){
System.out.println("Procesando Pepe...");
}
public static void main(String[] args) {
Humano pepe = new Pepe();
new EjemploSobrecarga().hacerAlgo(pepe);
}
}
La salida será: "Procesando un Humano...".
Aunque la instancia que se pasa como parámetro al método 'hacerAlgo()' es de la clase Pepe, el compilador solo sabe que la variable es una referencia a la clase Humano y por tanto decide invocar la versión que recibe tiene esta clase como argumento. La salida sería diferente si la variable se creará así: 'Pepe pepe = new Pepe();', en este caso el compilador reconoce una referencia a Pepe e invoca el método correspondiente.
Algo que nunca me ha quedado muy claro es que sucede cuando se pasa null como argumento, no se cuál es el criterio para elegir el método. En el ejemplo anterior que sucedería con la línea: 'new EjemploSobrecarga().hacerAlgo(null);'
El Final
Es muy importante reconocer si un método se está sobreescribiendo, sobrecargando o ambos. Hay que reconocer cuando hay errores de compilación o cuando una mala sobreescritura termina en una sobrecarga; esto se debe complementar muy bien con los conceptos de polimorfismo para que dado cualquier código, con la complejidad que sea, podamos predecir la salida.
En SProgramando he publicado un listado de preguntas sobre el tema.
Posted at 02:09PM jun 25, 2007 by Carlos Alexander Zuluaga in Java | Comentarios[7]
miércoles jun 20, 2007
Los Recursos
A través de un sitio que hace referencia a este weblog, encontré una buena página con recursos para la certificación. Se trata del Grupo de Usuarios Java del Uruguay que tiene una sección dedicada a la SCJP en donde tienen algunos artículos publicados y otra sección aún más interesante con preguntas y su solución bien explicada.
A mi modo de ver una de las mejores formas de estudiar es ver preguntas y entender bien su solución por que aunque en el examen no importa si se responde por suerte o conocimiento, para efectos educativos (y de la vida en general ;-) es mejor entender como funcionan las cosas por dentro y aprender más que casos concretos, reglas generales. Claro, otra recomendación es hacerlo una vez se acabe de leer la sección correspondiente en el libro guía.
Estos son los enlaces:
- Grupo de Usuarios Java del Uruguay: http://juguy.org/
- Artículos Sobre SCJP: http://juguy.org/content/category/7/75/58/
- Preguntas con Solución detallada: http://juguy.org/content/category/7/76/49/
Más material para el repositorio!.
Posted at 03:25PM jun 20, 2007 by Carlos Alexander Zuluaga in Java |
miércoles jun 06, 2007
SCJP for JSE5 - 8
1. ENCAPSULACIÓN.
El objetivo de la encapsulación es lograr código que se pueda modificar sin afectar el código de los demás. Esto se logra poniendo en práctica algunas recomendaciones:
- Proteja los atributos de instancia. (con algún modificador de acceso).
- Cree métodos públicos para acceder a los atributos, para ello use los estándares JavaBean para los métodos set() y get().
Ejemplo de una clase bien encapsulada:
class Algo{
private int propiedad;
public int getPropiedad(){
return propiedad;
}
public void setPropiedad(int valor){
propiedad = valor;
}
}
2. HERENCIA, IS-A (ES-UN) Y HAS-A (TIENE-UN).
La herencia es el mecanismo por excelencia de la programación orientada a objetos para reutilizar el código. La herencia permite el diseño de relaciones complejas entre los diferentes componentes de una aplicación y una de las cosas más importantes, el polimorfismo, que permite que cualquier clase sea tratada como uno de sus padres (algunas de las clases de las que hereda).
Dado el código:
class A{
public void hacerCosas(){
System.out.println("A está haciendo alguna cosa");
}
}
class B extends A{ }
class C extends B{ }
Las clases B y C heredan de A. B lo hace directamente y C lo hace a través de B; esto quiere decir que tanto B como C pueden ser tratadas como cualquiera de sus padres, esto es, B puede ser tratada como A y, C puede ser A o B. El siguiente código por ejemplo, es válido y la salida será 4 veces la frase "A está haciendo alguna cosa".
public static void main(String[] args) {
A clase1 = new A();
A clase2 = new B();
A clase3 = new C();
B clase4 = new C();
clase1.hacerCosas();
clase2.hacerCosas();
clase3.hacerCosas();
clase4.hacerCosas();
}
Lo mismo ocurre con los parámetros:
public static void unParametroGeneral(A parametro){
parametro.hacerCosas();
}
public static void main(String[] args) {
unParametroGeneral(new A());
unParametroGeneral(new B());
unParametroGeneral(new C());
}
El problema al tratar las clases como su padre, es que solo se puede acceder a los métodos y atributos de instancia declarados por el mismo padre; para acceder a los métodos y atributos específicos de cada clase, se debe usar una variable de su tipo.
IS-A y HAS-A
La relación IS-A (ES-UN) es una forma de decir "este objeto es de tipo XXX" y se refiere a la herencia entre clases o a la implementación de una interface específica.
Suponiendo que A y B son clases y que Y y Z son interfaces:
- A IS-A (ES-UN) B si A hereda de B directa o indirectamente.
- A IS-A X si A implementa X.
- X IS-A Y si X hereda directa o indirectamente de Y.
La relación HAS-A es mucho más simple y se refiere únicamente al uso que una clase hace de otra. Se dice que A HAS-A (TIENE-UN) B si A tiene un atributo de tipo B. Por ejemplo:
class A{}
class B{
private A atributo;
}
En este caso la clase B HAS-A A.
3. POLIMORFISMO
El polimorfismo se refiere a la capacidad de un objeto para comportarse de diferentes formas (poli-mórifico), esto en Java está dado por las clases de las que hereda y las interfaces que implementa, ambos, directa o indirectamente.
Un objeto puede ser tratado (lo mencioné anteriormente) como cualquiera de sus padres o las interfaces que implemente, es decir, como cualquiera de las clases o interfaces con las que pase el test IS-A, y claro, solo puede invocar los métodos que estén declarados en la clase que se use como tipo para la variable que apunta al objeto. Los métodos que se ejecutarán serán los de la instancia de la clase que realmente se haya creado.
Todo esto se entiende mejor con un ejemplo.
class Animal{
public void comer(){
System.out.println("Comiendo como Animal");
}
}
class Perro extends Animal{ }
class Labrador extends Perro{
public void comer(){
System.out.println("Comiendo como Labrador");
}
public void hacerCosasDivertidasDeLabrador(){
System.out.println("Soy un Perro Labrador haciendo cosas divertidas");
}
}
En este caso, las instancias de la clase Perro pueden ser tratadas como Animal o Perro y las de Labrador como Animal, Perro y Labrador. Pero claro, si por ejemplo se quiere acceder al método 'hacerCosasDivertidasDeLabrador()' de la clase Labrador debe hacerse a través de una variable de tipo Labrador.
Veamos algo de código:
public static void main(String[] args) {
Animal an = new Animal();
Animal pe = new Perro();
Animal la = new Labrador();
an.comer();
pe.comer();
la.comer();
}
Con el tipo Animal se pueden referenciar las instancias de las tres clases, por que Animal, Perro y Labrador cumplen el test 'IS-A Animal'. Pero los métodos que se acceden corresponden a cada una de las instancias que realmente se crearon. Al ejecutar la clase la salida es:
Comiendo como Animal
Comiendo como Animal
Comiendo como Labrador
Las líneas 'an.comer();' y 'pe.comer()' invocan ambos el mismo método implementado en la clase Animal, por que la clase Perro no tiene ninguno declarado, esto es, lo hace a través de herencia. La línea 'la.comer();' aunque tenga una referencia a través de la clase Animal, invoca el método implementado en la clase Perro por que es la instancia a la que realmente apunta.
Si introdujéramos la siguiente línea 'la.hacerCosasDivertidasDeLabrador()', tendríamos un error de compilación por que aunque la instancia en verdad sea de Labrador, la referencia a través Animal no tiene ni idea sobre la existencia del método. Un buen truco para descifrar este tipo de errores es que los llamados a los métodos se definen en tiempo de compilación, es decir, si el compilador no encuentra una relación directa entre la variable usada (herencia, implementación de una interface) y el método a llamar, lo único que puede hacer es mostrar un error. Pero esto que es clave, lo intentaré explicar mejor en otra entrada. Finalmente, para invocar correctamente el método 'hacerCosasDivertidasDeLabrador()', debemos crear una instancia de la clase Labrador y apuntar a ella (aunque a mucha gente no le gusta usar la palabra apuntador en Java) con una variable de ese mismo tipo, todo este enredo textual en dos líneas de código:
Labrador labra = new Labrador();
labra.hacerCosasDivertidasDeLabrador();
Algo muy importante saber y que debe ser entendido y practicado muy bien es que lo único que se selecciona dinámicamente basado en el objeto actual (en vez de la referencia) son los métodos de instancia. No sucede ni con los métodos estáticos ni con los atributos.
El Final.
De estos temas suelen salir las preguntas más complejas del examen (sin olvidarnos de los hilos) y es clave tenerlos muy claros, por eso es importante resolver un buen número de preguntas que los abarquen.
Posted at 12:19AM jun 06, 2007 by Carlos Alexander Zuluaga in Java | Comentarios[1]
martes jun 05, 2007
Y yo que creía que no se podía
NOMBRES DE VARIABLES
Alguna vez me pregunté si una variable podía tener el mismo nombre de una clase del paquete java.lang o en general, ¿una variable puede tener el mismo nombre de una clase que se esté importando?. A ver:
package whatyoubelievedyoucouldnot;
import javax.swing.JButton;
public class VariableName {
public static void main(String[] args) {
String Integer = "Hola Paco";
System.out.println("Tamanio: " + Integer.length());
System.out.println("Texto: " + Integer);
Integer JButton = Integer.length()*2;
System.out.println("JButton: " + JButton);
}
}
Si se compila y ejecuta la clase anterior la salida será:
Tamanio: 9
Texto: Hola Paco
JButton: 18
Pues resulta que sí, si se puede usar el nombre de cualquier clase como variable. ¿Pero qué pasa si necesito usar la clase java.lang.Integer?, pues eso, hay que cualificarla completamente:
System.out.println(java.lang.Integer.toHexString(0xCAFE));
String unNumero = "666";
Float Float = java.lang.Float.parseFloat(unNumero);
Aunque el código es como para moler a golpes quien lo haga, según las reglas no hay razón por la que no se puedan usar estos nombres ya que no son palabras reservadas y tienen nombres válidos.
Eso sí, según las buenas prácticas los nombres de variables y atributos deben empezar con letras minusculas, pero esto no es ningún impedimento para que una clase compile.
NOMBRES DE CLASES
¿Y qué hay sobre las clases?, pues lo mismo, como el nombre es válido, puedo hacer lo mismo. Veamos la siguiente obra de arte:
package whatyoubelievedyoucouldnot;
public class Thread {
@Override
public String toString(){
return "Soy un hilo farsante";
}
public static void main(String[] args) {
Thread Thread = new Thread();
System.out.println(Thread.toString());
}
}
También asesinaría a quien cometa este tipo de aberración, pero es válida. La salida al ejecutar este programa es "Soy un hilo farsante", por qué el método toString() que se ejecuta es el de la clase 'whatyoubelievedyoucouldnot.Thread'. ¿Qué tal si queremos un hilo de los 'verdaderos'?
package whatyoubelievedyoucouldnot;
public class Thread {
@Override
public String toString(){
return "Soy un hilo farsante";
}
public static void main(String[] args) {
java.lang.Thread Thread = new java.lang.Thread();
System.out.println(Thread.toString());
}
}
Igual, se cualifica la clase completamente para diferenciarla de la actual.
Hemos visto verdaderas obras del desastre y la confusión, supongo que si todo el código que hiciéramos fuera así, nadie se atrevería a ser programador. Aunque se ven tantas cosas...
Bien, y ¿qué pasa si hago una clase llamada Thread dentro de un paquete java.lang?, pues ese asunto ya toca con classloaders, el orden en que se cargan las clases, etc. algo para probar en casa y con lo que no me quiero meter.
Posted at 02:28PM jun 05, 2007 by Carlos Alexander Zuluaga in Java | Comentarios[2]
lunes may 28, 2007
SCJP for JSE5 - 7
1. Variables locales y modificadores de acceso
Las variables locales son las que se declaran dentro de los métodos y el único modificador que se puede usar con ellas es final, esto incluye los parámetros que recibe un método. Las variables primitivas marcadas como final no pueden ser modificadas y a las variables de referencia marcadas como final, no se les puede asignar un nuevo objeto.
2. Otras consideraciones para el modificador final
- Los métodos marcados como final no pueden ser sobreescritos.
- Los argumentos marcados como final no pueden ser modificados.
public int consultarDatos(final int clave){
clave = 23; //error de compilación!!
}
Este método no compila por que se está intentando modificar una variable final.
- Las clases final no pueden ser heredadas.
3. Otros modificadores
- abstract: El modificador abstract solo aplica para las clases y los métodos. Un método abstracto es el que ha sido declarado pero no tiene implementación y solo pueden existir dentro de clases abstractas. La primera clase concreta que hereda una clase abstracta, debe implementar todos sus métodos abstractos. Por clase concreta se quiere decir clase no abstracta.
- synchronized: este modificador solo aplica para los métodos. Un método sincronizado solo puede ser ejecutado por un hilo a la vez, esto se verá más adelante cuando se trate el tema de concurrencia.
- native: este modificador solo aplica para los métodos. Un método nativo es implementando normalmente en otro lenguaje para cada plataforma y en la clase solo se declara (como un método abstracto).
- strictfp: aplica para clases y métodos. Una clase o método marcada como strictfp se adhiere al estándar IEEE 754, no es necesario profundizar más (solo si se tiene curiosidad).
- transient: solo aplica para atributos de instancia. Los atributos marcados como transient son ignorados cuando se serializa una clase (tema tratado más adelante).
- volatile: solo aplica para atributos de instancia. Es un modificador utilizado cuando se trabaja con hilos, e indica a la máquina virtual que siempre acceda a una copia de la variable. Esto es complejo y se tratará junto con todo el tema de concurrencia.
4. Combinaciones entre modificadores
Los modificadores (que no son de acceso) que se pueden usar con los métodos son: final, abstract, synchronized y strictfp. Todos estos modificadores se pueden usar conjuntamente, pero no todos con todos. Las combinaciones legales se resumen en la siguiente tabla (una 'X' indica que se pueden combinar, una 'O' indica que no):
| final | abstract | synchronized | strictfp | |
| final | X | O | X | X |
| abstract | O | X | O | O |
| synchronized | X | O | X | X |
| strictfp | X | O | X | X |
Por ejemplo el modificador synchronized no se puede usar con abstract.
Los modificadores (que no son de acceso) que se pueden usar con atributos de instancia son: final, transient y volatile. Esta es la tabla para combinarlos:
| final | transient | volatile | |
| final | X | X | O |
| transient | X | X | X |
| volatile | O | X | X |
5. Métodos con argumentos variables
Los argumentos variables son una de las nuevas características de JSE5.0, e indican que cierto argumento puede recibir un número indeterminado de valores. Solo puede existir un argumento variable para un método y debe ser el último declarado:
void hacerCosas(int otroArgumento, String... valores){}
6. Declaración de constructores
Un constructor válido debe declararse como un método sin tipo de dato de retorno, con el mismo nombre de la clase, puede tener cualquier un modificador de acceso y no puede ser static, final o abstract (ninguno de los tres tiene sentido para un constructor).
public class Perro(){
//constructores válidos
Perro(){}
public Perro(){}
protected Perro(int valor){}
//private se suele usar para las clases singleton
private Perro(String ... args){}
}
7. Declaración de variables
Primitivas. Pueden ser de 8 tipos: char, boolean, byte, short, int, long, double o float.
Se pueden declarar varias al mismo tiempo e inicializarlas:
int a, b, c, d = 9;
short uno = 1, dos = 2, tres = 3;
Para las versiones anteriores del examen había que conocer los tamaños en bits o bytes de los tipos de datos, para esta versión solo es necesario saber que el orden ascendente de los tipos enteros es: byte, short, int y long; y que float es más pequeño que double.
De Referencia. Se declaran igual que las primitivas pero estas apuntan hacia objetos.
String a = "HOLA", b = null, c = new String("Otro Hola");
8. Variables de instancia
Son las que se declaran afuera de los métodos y existe una por cada instancia de la clase. Pueden modificarse con cualquiera de los cuatro niveles de acceso y usar los siguientes modificadores: final, transient y volatile. No pueden ser ni synchronized, strictfp, native ni static (con esto dejarían de ser variables de instancia).
9. Variables locales
Las variables locales o automáticas, son las que se declaran dentro de los métodos. Solo pueden usar el modificador final, para ellas no aplican los modificadores de acceso. Deben ser inicializadas antes de usarlas:
void hacerAlgoBienBacano(){
int valor; //variable local
System.out.println("valor = " + valor); //Error de compilación!!!
}
10. Declaración de arreglos
Se declaran de forma similar a cualquier dato primitivo o de referencia, pero se incluyen brackets a la derecha del tipo de dato o del nombre de la variable para indicar el número de dimensiones del arreglo.
String array[][]; //arreglo de Strings de dos dimensiones
int[] arr1, arr2[]; //arr1 es de una dimensión, pero arr2 es bidimensional
11. Variables y métodos static
El modificador static se usa para crear variables que existirán de forma independiente de cualquier instancia de la clase a la que pertenecen. El modificador static aplica para métodos, variables, clases internas y bloques de inicialización.
El modificador no aplica para constructores, clases, interfaces métodos de clases internas locales, variables de instancia (por que dejarían de ser de instancia) y variables locales.
12. Declaración de enums
Son otra de las nuevas características en JSE5.0. Se crearon para hacer un uso más seguro de las constantes, pues anteriormente se debían declarar como tipos de datos del lenguaje (generalmente primitivos), por lo que las variables podían tener cualquier valor de el dominio del tipo.
Con esta nueva característica es posible restringir el número de valores que puede tener una variable a un grupo de datos predefinido.
enum Animales{PERRO, GATO, MARRANITO, POLLITO};
Animales miMascota;
Con esta simple declaración la variable miMascota solo podrá tener valores que estén declarados detro del enum Animales.
Los enums se comportan de forma muy similar a las clases, es decir, solo pueden usar los modificadores de acceso por defecto (cuando no se digita nada) y public, cuando son públicos el nombre del enum debe ser el mismo del archivo que lo contiene. También pueden declararse dentro de clases (como las clases internas) e interactuar con los modificadores public, private, protected, static y strictfp. No pueden llevar los modificadores transient, volatile, synchronized ni abstract.
Los enums pueden tener constructores, variables de instancia, métodos y 'constant specific class body' (no me atrevo a traducirlo!). Todo se entiende mejor con un ejemplo.
enum TipoPollo {
//cada tipo de preparación tiene un número de calorias
CRUDO (10), ASADO (40), COCINADO (20), FRITO (100);
private int calorias;
//los valores que se pasan como argumento a la declaración
//de la variable, son pasados al constructor
TipoPollo(int calorias){
this.calorias = calorias;
}
public int getCalorias(){
return this.calorias;
}
public String getPresa(){
return "MUSLO";
}
}
Dada la anterior lista enumerada, podemos invocarla desde otra clase y ejecutar el siguiente método main():
public static void main(String[] args) {
TipoPollo unaPresa = TipoPollo.CRUDO;
System.out.println("El pollo está " + unaPresa.name());
System.out.println("El número de calorías es: " + unaPresa.getCalorias());
System.out.println("La presa que venden es: " + unaPresa.getPresa());
}
Ahora supongamos que el pollo frito viene en alas, el resto en muslos; se podría hacer empezar a preguntar por el tipo de presa (if (this.equals(FRITO)) return "ALA"; else return "MUSLO";)) o sobreescribir el método getPresa() del valor FRITO:
enum TipoPollo {
//cada tipo de preparación tiene un número de calorias
CRUDO (10), ASADO (40), COCINADO (20),
FRITO (100){
@Override
public String getPresa(){
return "ALA";
}
};
private int calorias;
//los valores que se pasan como argumento a la declaración
//de la variable, son pasados al constructor
TipoPollo(int calorias){
this.calorias = calorias;
}
public int getCalorias(){
return this.calorias;
}
public String getPresa(){
return "MUSLO";
}
}
Así, al ejecutar el método main():
public static void main(String[] args) {
TipoPollo unaPresa = TipoPollo.FRITO;
System.out.println("El pollo está " + unaPresa.name());
System.out.println("El número de calorías es: " + unaPresa.getCalorias());
System.out.println("La presa que venden es: " + unaPresa.getPresa());
}
La salida sería:
El pollo está FRITO
El número de calorías es: 100
La presa que venden es: ALA
Algunas conclusiones:
- Los constructores de los enums no pueden ser invocados directamente.
- Se puede definir más de un argumento para el constructor (aunque esto no se concluye del código que puse de ejemplo :-]).
- Las constantes pueden sobreescribir los métodos de la lista enumerada.
El Final
Hasta acá el final del primer capítulo del libro guía, es bueno practicar bastante esta primera parte por que es la base para el resto de capítulos, hay muchas preguntas llenas de marañas y procesos complejos cuya respuesta será un simple "compilation fails" y se identificará fácilmente si se tienen claros estos conceptos.
En sprogramando monté un listado de preguntas sobre el capítulo que pueden servir para evaluar el conocimiento sobre el tema, así que a practicar, la url es: http://sprogramando.wikidot.com/scjp-for-jse5-preguntas-grupo1.
Si hay errores en este texto, los anteriores o las preguntas, por favor escribirlo en los comentarios.
Un saludo.
Posted at 11:00PM may 28, 2007 by Carlos Alexander Zuluaga in Java | Comentarios[4]
domingo may 27, 2007
SCJP for JSE5 - 6
Acabo de crear una página donde reúno todos los recursos que voy encontrando con material para la certificación.
Por ahora están los links que he publicado acá mismo y adicionalmente las preguntas que hago sobre cada tema. Pero como la idea es que sea algo más o menos grande y que sirva de verdad, los recursos (links, preguntas, etc) que pongan en los comentarios o envíen a mi correo (alexander.zuluaga[arroba]gmail.com), los iré publicando. Insisto, sobre todo hacen falta preguntas.
La página completa: http://sprogramando.wikidot.com/scjp-for-jse5.
Las preguntas: http://sprogramando.wikidot.com/preguntas-scjp-for-jse5-preguntas.
Pues nada, esperemos a ver como funciona esto.
Posted at 11:26PM may 27, 2007 by Carlos Alexander Zuluaga in Java |
sábado may 26, 2007
SCJP for JSE5 - 5
1. DECLARACIÓN DE MÉTODOS Y ATRIBUTOS (MIEMBROS)
Los atributos y métodos de las clases tienen siempre un nivel de acceso aunque éste no sea digitado. Existen cuatro niveles: public, protected, default y private, cada uno de ellos restringiendo o permitiendo el acceso a los miembros de las clases, pero, es muy importante saber que si una clase no puede "ver" a la otra, mucho menos podrá ver sus miembros; esto ocurre en el caso de una clase con modificador de acceso default (cuando no se digita nada) que se intenta acceder desde una clase en un paquete diferente. Por ejemplo:
package uno;
class Servidor{
//muchos métodos y atributos
}
package dos;
public class Cliente{
//otro montón de métodos tratando de acceder a Servidor
}
En este caso no importan los modificadores de acceso de los miembros de la clase Servidor, por tener esta el tipo de acceso por defecto (default), la clase Cliente nunca podrá acceder a sus atributos o métodos, a menos claro, que se modifiquen para pertenecer al mismo paquete.
Una clase solo puede invocar los métodos o ver los valores de los atributos de otra, cuando ésta última es pública o se encuentran en el mismo paquete, ahí si empiezan a aplicar los modificadores de acceso de los miembros.
Miembros public
Los miembros marcados como public pueden ser accedidos desde cualquier otra clase a través de una instancia o por herencia.
Ejemplo:
package utilidades;
public class Sumas {
public int sumar(int num1, int num2){
return num1 + num2;
}
}
package operaciones;
import utilidades.Sumas;
//nivel de acceso default
class AlgoRaro {
public void realizarCalculosCientificos(){
Sumas s = new Sumas();
//acceso a través de una instancia
int resultado = s.sumar(4,4);
System.out.println("El resultado es: " + resultado);
}
}
En este ejemplo la clase AlgoRaro puede acceder el método sumar() de la clase Sumas por que aunque están en diferentes paquetes la clase Sumas es pública al igual que el método sumar(), y el acceso se hace a través de una instancia de la clase. Lo contrario no se podría hacer, es decir, la clase Sumas no tiene acceso a ninguno de los métodos o atributos de la clase AlgoRaro por que están en diferente paquete y AlgoRaro tiene acceso por defecto, lo que descarta de una vez cualquier tipo de visibilidad.
Miembros private
Los miembros private solo pueden ser accedidos por la clase a la que pertenecen, de ninguna forma (claro, que las hay las hay: reflection) se pueden invocar desde clases externas.
Ejemplo (modificando el anterior):
package utilidades;
public class Sumas {
private int sumar(int num1, int num2){
return num1 + num2;
}
void cualquierOtraCosa(){
int valor = sumar(1,2); //se puede
}
}
package operaciones;
import utilidades.Sumas;
class AlgoRaro {
public void realizarCalculosCientificos(){
Sumas s = new Sumas();
int resultado = s.sumar(4,4); //error de compilación!!!
System.out.println("El resultado es: " + resultado);
}
}
En este caso, la clase AlgoRaro no puede acceder al método sumar() por que es privado, en cambio, el método cualquierOtraCosa() si puede invocarlo por que pertenece a la misma clase.
Miembros default
Los miembros con nivel de acceso default pueden ser accedidos solo por clases que pertenezcan al mismo paquete de la clase que los contiene:
package package1;
public class Valores {
final double PI = 3.14159;
}
package package1;
public class Circunferencia {
public double calcularArea(double radio){
Valores v = new Valores();
return v.PI * radio * radio;
}
}
El atributo de instancia PI, puede ser accedido por la clase Circunferencia por que se encuentran en el mismo paquete, sin embargo:
package package2;
public class Circunferencia {
public double calcularArea(double radio){
Valores v = new Valores();
return v.PI * radio * radio; //Error de compilación!!
}
}
Arroja un error de compilación por que los paquetes son diferentes.
Miembros protected
El nivel de acceso protected es el más particular: una clase puede acceder a los miembros protected de otra si pertenece al mismo paquete o hereda de ella. En el primer caso accede a través de una instancia y el el segundo se invoca directamente. Es decir, el nivel de acceso protected es el mismo default + herencia.
Dada la clase:
package package2;
public class ClaseConMiembroProtegido {
protected String demeAlgo(){
return "Tome un 5";
}
}
El método demeAlgo() se puede acceder a través de una instancia si la clase pertenece al mismo paquete:
package package2;
class AccesoPorPaquete{
void accederAUnMiembro(){
ClaseConMiembroProtegido cc = new ClaseConMiembroProtegido();
System.out.println(cc.demeAlgo());
}
}
O se puede invocar a través de la herencia, simplemente digitando el nombre del método, pertenezca o no al mismo paquete:
package package1;
import package2.ClaseConMiembroProtegido;
class AccesoPorHerencia extends ClaseConMiembroProtegido{
void accederPorHerencia(){
System.out.println("Accediendo por herencia: " + demeAlgo());
}
}
Ojo!!, nos podemos llevar una sorpresa si en el segundo caso intentamos acceder el método demeAlgo() a través de una instancia:
package package1;
import package2.ClaseConMiembroProtegido;
class AccesoPorHerencia extends ClaseConMiembroProtegido{
void accederPorHerencia(){
ClaseConMiembroProtegido cc = new ClaseConMiembroProtegido();
System.out.println("Accediendo por herencia: " + cc.demeAlgo());
//error de compilación!!!
}
}
Esto sucede por que la clase AccesoPorHerencia no pertenece al mismo paquete de ClaseConMiembroProtegido y solo puede acceder sus miembros a través de la herencia.
El Final
Esto es todo en cuando a modificadores de acceso para los miembros de las clases. Es uno de los temas que más claros hay que tener, pues en el examen hay muchas preguntas alrededor y la "flexibilidad" de todo esto, es un punto que juega en nuestra contra.
Posted at 02:06PM may 26, 2007 by Carlos Alexander Zuluaga in Java |
viernes may 25, 2007
SCJP for JSE5 - 4
Los resúmenes que publicaré no serán en lo absoluto guías definitivas para preparar el examen, lo hago más bien como un ejercicio personal para fortalecer lo que estudio y pueden servir para dar un repaso general a todos los temas.
La secuencia de los temas que abordaré estarán en el mismo orden del libro guía.
1. IDENTIFICADORES Y JAVABEANS
Los objetivos en cuanto a identificadores y JavaBeans consisten en diferenciar entre nombres válidos y nombres que cumplan con los estándares.
Un nombre válido es cualquiera que compile o más bien, un nombre no válido, es aquel que arrojará un error en tiempo de compilación. Un nombre válido debe cumplir tres reglas básicas:
- Debe empezar con una letra, el caracter underscore (_) o el signo pesos ($). Sí, eso del signo pesos para mí también fue toda una sorpresa.
- Después del primer caracter puede contener cualquier combinación de letras, números, underscore (_) y signo pesos ($).
- No puede ser una palabra reservada.
Los estándares de codificación propuestos para los JavaBeans son:
- Los atributos de las clases se deben consultar a través de métodos get<NombreAtributo> que no reciben argumentos y devuelven el mismo tipo de dato del atributo a consultar, y se deben modificar a través de métodos set<NombreAtributo> que reciben un argumento del mismo tipo del dato a modificar y no devuelven ningún valor (void). Opcionalmente si el atributo es de tipo boolean se puede consultar con un método is<NombreAtributo>.
- Los métodos setXX, getXX e isXX deben ser públicos.
- Los nombres de los métodos deben conformarse anteponiendo set, get o is al nombre del atributo y dejando la primera letra de este último en mayúscula. Por ejemplo el atributo edad de tipo int, tendría un método get así: public int getEdad() { return this.edad}.
- Los eventos de los JavaBeans se gestionan con Listener que reciben la notificación cuando se ejecuta cierto evento. La nomenclatura para agregar y eliminar Listeners a un JavaBean debe ser: addXxxListener(XxxListener xx) y removeXxxListener(XxxListener xx).
Palabras Reservadas
Son todas esas que usa el lenguaje para nombrar elementos propios como los tipos de datos (int, short), los modificadores de acceso (public, private), etc. todos los días usamos muchas de las palabras reservadas y la lista completa se puede ver aquí, sin embargo hay muchas palabras que no sabía que eran reservadas o no las había usado nunca: const, continue, goto, native, strictfp, transient, volatile, assert y enum.
Las palabras const y goto no las usa el lenguaje pero las deja reservadas para evitar confusiones en el código. Y finalmente la palabra enum solo existe a partir de la versión 5.
2. DECLARACIÓN DE CLASES
La estructura de una clase en Java es la siguiente:
<package> //opcional
<imports> //opcional
<modificador de acceso (opcional)> class <nombre de la clase>{
<atributos y métodos>
}
Hay secciones opcionales, pero cuando estén presentes deben ir en ese orden estricto, de hecho la declaración más simple y válida de una clase es: class Simple{}. Los comentarios pueden ir en cualquier línea sin importar el orden.
Un solo archivo puede tener cualquier número de clases, pero, solo puede haber una clase pública y el nombre de ésta debe coincidir con el nombre del archivo; claro, puede haber un número indeterminado de clases no públicas y el nombre del archivo no tiene que coincidir con ninguna, y, también se puede compilar un archivo sin ninguna clase definida.
Las clases solo tiene dos tipos de acceso: por defecto y público. El acceso por defecto es que se le asigna cuando no tienen ningún modificador de acceso y hace que la clase solo pueda ser vista por otras clases que pertenezcan al mismo paquete. El acceso público se asigna antecediendo la palabra class por el modificador public y permite que cualquier clase de cualquier paquete pueda acceder a ella.
Otros Modificadores
Las clases pueden tener tres modificadores diferentes a los de acceso: final, strictfp, abstract. Puede tener varios a la vez, pero no todos, ni siquiera cualquier combinación.
final: una clase marcada como final (final class FinalClass{}) no puede ser heredada (class LastFinalClass extends FinalClass{}). Clases como Integer, Float y String, son ejemplos de clases final.
strictfp: (¿Strict floating point?) este modificador indica que los métodos de la clase cumplirán con las reglas IEEE 754 para los números con coma flotante. Esto no tiene mucha relevancia para el examen, pero hay algo más de información aquí. Lo importante de este modificador es que se puede aplicar a cualquier clase o interface y a los métodos que no sean abstractos.
abstract: las clases abstractas no pueden ser instanciadas y pueden contener método abstractos (sin implementación). Como dice el libro "son clases cuya misión en la vida es ser heredadas". Como es de esperarse, un método abstracto no puede tener el modificador strictfp, pues los detalles de su implementación se desconoce.
3. DECLARACIÓN DE INTERFACES
Las interfaces se pueden pensar como "contratos" que especifican lo que una clase puede hacer. Técnicamente son clases 100% abstractas, es decir, todos los métodos son abstractos y sin implementación. Los métodos de una Interface son implícitamente (aunque se puede, no hay que usar los modificadores) públicos y abstractos, por tanto no pueden tener los modificadores strictfp, final o native.
Las interfaces también pueden tener variables que implícitamente son public, final y static y, al igual que los métodos, se pueden usar estos modificadores pero no es necesario (a veces por legibilidad es bueno usarlos o no). Según lo dicho, la siguientes tres interfaces son equivalentes:
public interface Calculable{
// no hay que usar ningún modificador, es explícito
int OPERACION = 1;
int calcular();
}
public abstract interface Calculable{
// se pueden poner todos los modificadores
public static final int OPERACION = 1;
public abstract int calcular();
}
public interface Calculable{
// aunque se declaren solo algunos modificadores,
// en tiempo de ejecución los tendrá todos
final int OPERACION = 1;
public int calcular();
}
Al igual que las clases, solo puede existir una interface pública por archivo y deben tener el mismo nombre (el archivo y la interface), pero pueden haber varias interfaces no públicas en el mismo archivo.
Note también que los atributos de una interface deben ser inicializados al declararlos, pues al tener el modificador final, no pueden ser modificados posteriormente.
El Final
El objetivo hasta este momento solo es saber como se declaran atributos, clases e interfaces, más adelante veremos en acción todos estos elementos.
Recurso
A mi bandeja de entrada ha llegado la dirección de una página con un listado de preguntas para prácticar: http://j2eerojak.blogspot. com/2007/05/scjp-310-035-sample-questions-1.html.
(Para visitar la página, eliminar el espacio que hay después de 'blogspot.', no se por que roller no me deja grabar cuando la cadena está completa, debe ser que no le gusta 'blogspot[PUNTO]com' :-])
Un saludo.
Posted at 10:51AM may 25, 2007 by Carlos Alexander Zuluaga in Java | Comentarios[2]
jueves may 24, 2007
SCJP for JSE5 - 3
Antes de empezar a estudiar con la metodología de la certificación, preferí buscar algún tutorial, manual o documento sobre las nuevas características de JSE y sobre los temas que no evaluaron en la versión anterior del examen. La metodología de estudio para la certificación empieza y termina con un nivel de profundidad y tecnicismos, que puede resultar incomprensible sino se tiene algo de experiencia en el tema que se está estudiando, ya sea una característica del lenguaje o un API.
A lo que me refiero exactamente es que para abordar el tipo de preguntas y conocimiento necesario para una certificación, es necesario ver el tema como una solución a un problema, es decir, usar el API o la característica del lenguaje para desarrollar algún programa de acuerdo a unos requerimientos, ya sean del mundo real o inventados.
Pues buscando, encontré una buenísima presentación y varios laboratorios de Sang Shin. Se trata de un curso de un día sobre las nuevas características de JSE 5, que incluyen una presentación y cuatro laboratorios que nos dejarán completamente enterados sobre los temas. Como para la certificación no es necesario conocer sobre JMX, los dos últimos laboratorios se pueden excluir y una buena guía para introducirnos al mundo de JSE 5 puede ser:
- J2SE 5.0 Overview: la presentación sobre las nuevas características.
- The Tiger Roars! Learn The New Functionality of J2SE 5.0: laboratorio para explorar la sintáxis, APIs y demás cuestiones de la nueva funcionalidad. Incluye Autoboxing, Generics, Type-Safe Enumerations, Enhanced For Loops, Static Imports, Variable Arguments, Metadata (annotations) y Concurrency Utilities. El único tema que no aparece en el examen es annotations, el resto, obligado estudiarlos.
- Learn J2SE 5.0 Generics Using NetBeans 4.0: solo para quienes estén interesados en practicar un poco más Generics, aunque la versión de NetBeans propuesta está desactualizada, se puede hacer con la versión 5.5 sin problemas.
Claro, para quienes no hayan tenido ningún contacto con los temas a evaluar, siempre será útil hacer el curso completo "Java Programming With Passion", o si hay dudas con APIs específicos, también se pueden estudiar independientemente. Yo estudiaré al menos I/O and NetWorking:
Un saludo.
Posted at 12:09PM may 24, 2007 by Carlos Alexander Zuluaga in Java | Comentarios[1]
miércoles may 23, 2007
SCJP for JSE5 - 2
Recursos
Comparado con lo que se encuentra para la certificación de JSE 1.4, hay muy poco para la de JSE 5, por eso voy a ir guardando todos los recursos que me encuentre en la red (gratuitos y de pago) para quizá crear una página (puede ser en sprogramando) o algo por el estilo, pero de seguro los voy a ir publicando en el Weblog a medida que los encuentre. Igualmente voy a tener en cuenta los que ingresen en los comentarios.
El Examen
Para quien no tiene una certificación previa en el lenguaje el examen a presentar es "Sun Certfied Programmer for the Java 2 Platform, Standard Edition 5.0 (CX-310-055)". Este examen incluye todos los conceptos básicos del lenguaje, es decir, desde cero. Los objetivos y temas a evaluar están claros en la página oficial (el link anterior).
Como yo ya tengo la certificación de la versión 1.4, el examen que debo presentar es "Sun Certified Programmer for the Java 2 Platform, Standard Edition 5.0 Upgrade Exam (CX-310-056)", que se enfoca en las nuevas características y cambios en el lenguaje con respecto a la versión anterior: generics, autoboxing, concurrency. Adicionalmente algunos de los objetivos del examen han cambiado y en este upgrade incluye también APIs que se evaluan a partir de esta versión: Serialization, *Reader, *Writer, *Stream, Regular Expressions, entre otras, el listado completo se puede ver en la página oficial.
El Libro
Al igual que para la certificación anterior, voy a seguir el libro "SCJP Sun Certified Programmer for Java 5 Study Guide" de Katherine Sierra y Bert Bates. Sin duda el libro es excelente:
- Abarca todo el contenido del examen.
- Es absolutamente didáctico, si lees la entrevista verás que Kathy y Bert han estudiado mucho sobre el funcionamiento del cerebro y saben de verdad como hacer para que aprendamos sobre cualquier tema.
- Al final de cada capítulo hay una serie de preguntas que al menos en la serie anterior, y no creo que esto haya cambiado, son o eran muy similares a las del examen, de hecho creo que hubo preguntas iguales pero diferentes valores.
- El hecho de que Kathy y Bert trabajen en la preparación de los exámenes de certificación deja claro que saben que se debe enseñar.
Los Recursos
Como apenas empiezo no hay mucho que publicar, pero seguro esto irá cambiando a medida que avance mi estudio y el Weblog. Aunque el examen de actualización incluye menos temas, voy a estudiar como si fuera a presentar el examen "normal" para repasar todos los temas, así que los recursos son de interés general.
Los siguientes links los obtuve de una entrada en el Weblog de Diego Campodónico:
- http://www.examulator.com/phezam/login.php: examen en línea con 60 preguntas (eso especifica la página). Solo aceptar el login y seleccionar "Java Test". Incluyen algunas con drag and drop, un nuevo estilo de preguntas con respecto a la versión anterior del examen.
- http://www.wickedlysmart.com/SCJPStudyGuide/Java_5_SCJPquestions.html: examen con 11 preguntas y respuestas (al final).
- http://java.boot.by/scjp-tiger: un completo resumen en línea con los temas a evaluar.
- http://www.javabeat.net/javabeat/scjp5/mocks/index.php: un buen grupo de preguntas discriminadas por tema.
El Final
Esto es todo por ahora, solo queda decir que alguna vez leí que el mejor indicador para saber si se está preparado para presentar en examen es sacar más o menos el 90% en la mayoría de los exámenes simulados. Así lo hice antes y esa es la intención ahora.
Posted at 05:57PM may 23, 2007 by Carlos Alexander Zuluaga in Java | Comentarios[1]
SCJP for JSE5
Hoy la situación ha cambiado, al menos casi todas las empresas y desarrollos que conozco, están trabajando sobre JSE5 o JSE6 (entre JSE5 y JSE6 el cambio no es tan dramático), he perdido alguna oportunidad por no estar certificado en JSE5 y sobre todo tengo ganas de actualizarme un poco en cuanto al lenguaje, y no hay mejor manera de hacerlo que estudiando para el examen de certificación.
Ya lo dije, un acercamiento a Java se puede hacer a través de tutoriales, artículos y weblogs; seguramente se conocerán nuevas APIs, métodos, clases e interfaces no muy conocidas y seguro se puede aprender a implementar un sistema operativo. Pero el examen se trata de otra cosa, no de conocer APIs tanto como de saber que sucede tras el telón y como pensaban quienes diseñaron el lenguaje, pero sobre todo es divertido, se pasa de estudiar por gusto a hacerlo por el reto de responder alguna pregunta o descifrar algún enigma:
¿Qué muestra la línea System.out.println(" PISALE PEPE: " + 7 + 8)?
¿Que muestra System.out.println(7 + 8 + " :PISALE PEPE")?
Pero sobre todo se trata de ¿Por qué?.
Pues bien, por todo lo anterior y algunas razones más, he decidido actualizar mi certificación de la 1.4 a la 5 y como otras tantas veces me lo he propuesto y no lo he cumplido, voy a tratar de que escribir para el Weblog se convierta en una actividad de repaso y en un método para fortalecer todo lo que estudio. También para ubicar algunos recursos en red (sobre todo preguntas de preparación) que sirvan para la preparación del examen, así que comentarios y sugerencias son bienvenidos.
Posted at 01:59PM may 23, 2007 by Carlos Alexander Zuluaga in Java | Comentarios[1]
lunes oct 31, 2005
[Apuntes]: String y StringBuffer
Una de las clases más importantes de Java es la clase String. La usamos todo el tiempo, todos los objetos tienen un método .toString() que indica su nombre completamente cualificado y su código hash, los datos de entrada se toman en forma de String la mayoría de las veces y así mismo representamos los resultados, etc; esto hace que sea de suma importancia en el aprendizaje del lenguaje y como no en los exámenes de certificación. StringBuffer se puede decir que tiene el mismo objetivo de String a la hora de representar los datos, pero está diseñado para contribuir con la optimización en el uso de memoria.
He estado estudíandolas y pongo por acá algunos apuntes; esperemos que algún día haya material suficiente para hacer un artículo completo.
java.lang.String
La clase String implementa el patrón de diseño inmutable y se puede decir que esta fortaleza igualmente se convierte en el quebradero que cabeza o en una de las fuentes más comunes de errores, pues en tiempo de ejecución es un objeto que no tiene el comportamiento habitual.
El patrón inmutable indica un objeto que no puede ser modificado y eso
es precisamente lo que sucede con la clase String, cuando creamos un objeto
este no puede cambiar más en el transcurso del programa y cada instrucción
que parezca lo debe modificar, en realidad está creando un nuevo objeto,
así que siempre debemos estar atentos con la respectiva referencia; igualmente
cuando hacemos comparaciones hay que tener mucho cuidado al emplear el operador
== o el método equals(), pues una cosa es comparar
las referencias y otra los valores.
Veamos la siguiente porción de código:
String s1 = "pisale ";
s1.concat("pepe");
System.out.println(s1);
Dará como salida "pisale". Aunque es cierto que
el método concat concatena el valor pasado como parámetro
a la cadena de caracteres, el objeto no puede cambiar y por tanto un nuevo objeto
de tipo String es creado con el valor "pisale pepe" y
como no es referenciado, simplemente pasa al pool donde están las cadenas
de caracteres que se han ido creando durante la ejecución del programa.
Obtenemos un resultado diferente si de nuevo asignamos la cadena generada a la variable s1:
String s1 = "pisale ";
s1 = s1.concat("pepe");
System.out.println(s1);
Aqí la salida generada si será "pisale pepe"
Este mismo comportamiente obtendremos con todos los métodos que modifiquen
una cadena, tales como toUpperCase(), toLowerCase(),
replace(char c1, char c2), etc.
Ahora, si queremos comparar dos String hay que saber que == compara
referencias a objetos, no valores; esto quiere decir que aunque dos cadenas
de caracteres tengan como valor "pisale", puede que al
compararlas la máquina virtual nos diga que estas no son iguales, ya
que no es suficiente que tengan el mismo valor sino que deben estar apuntando
hacia el mismo objeto almacenado en memoria, por ejemplo:
String s1 = "pepe";
String s2 = "pepe";
System.out.println("s1 == s2");
if (s1 == s2){
System.out.println("Somos igualitos!!");
}else{
System.out.println("Somos diferentes");
}
Dará como salida "somos igualitos!!" aún al modificarlo así:
String s1 = "pepe";
String s2 = "pe"+"pe";
System.out.println("s1 == s2");
if (s1 == s2){
System.out.println("Somos igualitos!!");
}else{
System.out.println("Somos diferentes");
}
El compilador automáticamente concatena las cadenas "pe" y
"pe". Esto forma una cadena "pepe" igual a la
que está apuntando s1 y como las toma del pool de String, ambas variables quedarán
con la misma referencia "pepe", por tanto la salida de este código
es "somos igualitos".
Otra cosa sucede cuando ejecutamos lo siguiente:
String s1 = "pepe";
String s2 = "pe";
String s3 = "pe";
String s4 = s2 + s3;
System.out.println("s1 == s2 --> "+s1 + " == "+s4);
if (s1 == s4){
System.out.println("Somos igualitos!!");
}else{
System.out.println("Somos diferentes");
}
Aquí el resultado de la comparación es "somos diferentes".¿Por
qué? la situación acá es que el objeto s4 se está
creando a partir de otros dos objetos para lo cual la máquina virtual
necesita crear un tercer objeto que finalmente tendrá el valor "pepe",
ojo!, sólamente el valor, no la misma referencia. Igual sucede con objetos
pasados por la línea de parámetros (cuando trabajamos por consola),
estos no se deben comparar con el operador == pues al parecer no se almacenan en
el pool de String.
¿Y qué hay con el método equals?
Bien, este método si compara los valores que contienen las cadenas, la referencia no importa. El siguiente código:
String s1 = "pepe";
String s2 = "pe";
String s3 = "pe";
String s4 = s2 + s3;
if (s1.equals(s4)){
System.out.println("iguales con equals");
}else{
System.out.println("diferentes???");
}
Tiene la salida: "iguales con equals" y lo mismo sucedería
con todas las comparaciones que hemos hecho anteriormente usando el operador
==.
Como última observación sobre String hay que anotar que aún si el mismo objeto se crea de forma diferente, la comparación de referencias no funcionará; por ejemplo:
String s1 = "pisale pepe";
String s2 = new String(s1);
if (s1 == s2){
System.out.println("Iguales con ==");
}else{
System.out.println("diferentes con ==");
}
if (s1.equals(s2)){
System.out.println("iguales con equals");
}else{
System.out.println("diferentes con equals");
}
Aunque usamos el objeto s1 para crear s2, la salida de este programa será:
diferentes con == iguales con equals
StringBuffer
La clase StringBuffer está diseñada para manejar cadenas de caracteres que deban cambiar varias veces durante la ejecucción de un programa. Veamos esta línea de código con:
String st = "pisale ";
st = st.concat("pepe");
Aquí se ha creado una cadena de caracteres "pisale pepe" a la que está apuntando la variable st; lo malo es que el valor "pisale " creado inicialmente se va para el pool de String y sigue consumiendo memoria. Si pensamos en una aplicación grande o mediana y el montón de instrucciones similares que tenemos, nos damos cuenta de una de las desventajas del patrón inmutable y es su "excesivo" consumo de memoria, creando objetos por doquier.
La clase StringBuffer se pensó para solucionar esto y su principal característica es que representa una cadena de caracteres que puede cambiar, aquí no hay referencias perdidas y todas las modificaciones quedarán en el mismo objeto que estamos trabajando. Por ejemplo:
StringBuffer sb = new StringBuffer("pisale");
sb.append(" pepe");
Aquí en realidad la variable sb al final tendrá el valor "pisale
pepe" y tan solo se habrá creado un objeto que se luego se
modifica. Comparando ambas clases:
StringBuffer sb = new StringBuffer("pisale");
sb.append(" pepe");
sb.append(" con fuerza");
System.out.println("StringBuffer: "+sb);
String st = "pisale";
st.concat(" pepe");
st.concat(" con fuerza");
System.out.println("String: "+st);
Obtenemos:
StringBuffer: pisale pepe con fuerza String: pisale
Y mientras usando String hemos creado tres objetos("pisale", " pepe", " con fuerza"), con StringBuffer solo hemos creado uno y lo hemos modificado hasta obtener una sola cadena "pisale pepe con fuerza".
StringBuffer también tiene otros métodos bien "simpáticos"
y seguramente útiles en muchos casos como reverse que conviernte
"arroz" en "zorro" o insert
que permite insertar una cadena entre las posiciones indicadas, pero creo que
ya sería demasiado tratarlas en esta publicación, mejor las dejamos para
otro día.
Esto es todo por ahora, si hay algún error o sugerencia para que esto sea un artículo mejor redactado y estructurado lo tendré en cuenta,. por favor decirlo en los comentarios; si no, pues dejamos así y en la próxima hablo un poco sobre la clase Math y sus métodos.
Posted at 09:26PM oct 31, 2005 by Carlos Alexander Zuluaga in Java | Comentarios[4]