Grupo OSUM - UTN MDP


Hace unos días cree un grupo en la red social de sun OSUM (Open Source University Meetup). Este sitio es una comunidad de estudiantes, desarolladores, etc. Los miembros de la comunidad tienen acceso a cursos gratuitos, eventos, y muchos otros recursos para perfeccionar sus habilidades.
Existen diferentes grupos de universidades de todo el mundo. Y como mi universidad (la UTN Centro de estudios Mar del Plata) no tenía un grupo, me decidí a crear uno.
La idea de este grupo es compartir investigaciones, inquietudes y todo tipo de información de utilidad sobre JAVA y cualquier otra tecnología open source. Los invito a participar y unirse al grupo, así creamos un lugar que permita compartir las experiencias tanto de los egresados como de los que hoy en día siguen estudiando en la UTN mdp.
De más esta decir que cualquier persona (ajena a la UTN) que se quiera unir esta invitado a compartir todo tipo de tecnologías y experiencias. Les dejo el link a continuación:
http://osum.sun.com/group/utn_mdp

Operadores

A continuación dejo el resumen del cuarto capítulo (Operators) del libro "Sun Certified Programmer for Java 6 Study Guide".

Operadores relacionales (Objetivo 7.6):
  • Los operadores relacionales siempre retornan un valor boolean (true o false).
  • Existen seis operadores relacionales: <, <=, >, >=, == y !=. Los últimos dos también se pueden llamar operadores de igualdad.
  • Cuando se comparan caracteres, se utilizan los valores Unicode como valores numéricos.
  • Operadores de igualdad:
    - Existen dos operadores de igualdad == y !=.
    - Cuatro tipos de cosas pueden ser comparadas: números, caracteres, valores booleanos y variables de referencia.
  • Cuando se comparan variables de referencia, el operador == devuelve true solo si ambas referencias apuntan al mismo objeto.
El operador instanceof (Objetivo 7.6):
  • Instanceof se utiliza solo para variables de referencia, y verifica si un objeto es de un tipo particular (es decir, si cumple con la relación ES-UN).
  • El operador instanceof solo puede ser utilizado para comprobar objetos (o null) contra tipos de clases que estén en la misma jerarquía de clases.
  • Para las interfaces, un objeto pasa la prueba instanceof si su clase o alguna de sus superclases implementa la interface.
Operadores aritméticos (Objetivo 7.6):
  • Existen cuatro operadores matemáticos primarios: adición, sustracción, multiplicación y división.
  • El operador de resto (%), retorna el resto de la división entera.
  • Las expresiones son evaluadas de izquierda a derecha, a menos que se utilicen paréntesis, o a menos que algunos operadores en la expresión tengan mayor precedencia que otros.
  • Los operadores *, / y % tienen mayor precedencia que + y -.
Operador de concatenación de Strings (Objetivo 7.6):
  • Si ambos operandos son Strings, el operador + los concatena.
  • Si ambos operandos son numéricos, el operador + suma sus valores.
Operadores de incremento y reducción (Objetivo 7.6):
  • Los operadores prefijo (++ y --) se ejecutan antes de que el valor sea utilizado en la expresión.
  • Los operadores sufijo (++ y --) se ejecutan después de que el valor sea utilizado en la expresión.
  • En cualquier expresión, ambos operandos son evaluados completamente antes de que el operador sea aplicado.
  • Las variables final no pueden ser incrementadas o reducidas.
Operador condicional ternario (Objetivo 7.6):
  • Devuelve un valor basado en si una expresión booleana es true o false. (expresion) ? (valor_true) : (valor_false);
    - El valor luego del ? se devuelve si la expresión es verdadera (o true).
    - El valor luego del : se devuelve si la expresión es falsa (o false).
Operadores lógicos (Objetivo 7.6):
  • El examen subre seis operadores lógicos: &, |, ^, !, && y ||.
  • Los operadores lógicos trabajan con dos expresiones (excepto el operador !) que deben resolver a expresiones booleanas.
  • Los operadores && y & devuelven true solo si ambos operandos son true.
  • Los operadores || y | devuelven true si algunos de los dos operandos es true.
  • Los operadores && y || son conocidos como operadores de corto-circuitos.
  • El operador && no evalúa el operando derecho si el izquierdo es false.
  • El operador || no evalúa el operando derecho si el izquierdo es true.
  • Los operadores & y | siempre evalúan ambos operandos.
  • El operador ^ (denominado “XOR lógico”), devuelve true solo si los valores de los operandos son distintos entre sí (si son iguales devuelve false).
  • El operador ! (denominado operador de “inversión”), devuelve el valor opuesto del operando que precede.

Ejemplos de código - Asignaciones

A continuación dejo unos ejemplos de código con explicaciones. Estos ejemplos surgieron a partir de la realización de los ejercicios del capítulo 3 (Assignments) del libro "Sun Certified Programmer for Java 6 Study Guide".
Para ver el post con el resumen de dicho capítulo haz click aquí.
Cada ejemplo tiene un link para visualizar el código original desde el proyecto (SVN) google.

EJEMPLO 1 (Ver código original)

01. class Alien {
02. String invade(short ships) { return "a few"; }
03. String invade(short... ships) { return "many"; }
04. }
05. public class Defender {
06. public static void main(String[] args) {
07. System.out.println(new Alien().invade(7));
08. }
09. }
/*
Defender.java:7: cannot find symbol
symbol : method invade(int)
location: class Alien
System.out.println(new Alien().invade(7));
^
1 error
*/

Al compilar el código anterior se produce un error en la línea 7. Esto sucede debido a que se pasa como argumento el valor 7. El literal 7 (a secas) es de tipo int. Como no existe ninguna versión del método invade() que tome un valor int, se genera el error "cannot find symbol".
Para que este ejemplo funcione correctamente es necesario realizar un casteo del literal 7 hacia el tipo short, como lo muestra la línea siguiente:
System.out.println(new Alien().invade((short) 7));
De este modo el programa se ejecutaría correctamente, y su salida sería:
a few

EJEMPLO 2 (Ver código original)

01. public class Dims {
02. public static void main(String[] args) {
03. int[][] a = {{1, 2, }, {3, 4}};
04. int[] b = (int[]) a[1];
05. Object o1 = a;
06. int[][] a2 = (int[][]) o1;
07. int[] b2 = (int[]) o1;
08. System.out.println(b[1]);
09. }
10. }
/*
Exception in thread "main" java.lang.ClassCastException: [[I cannot be cast to [
I
at Dims.main(Dims.java:7)
*/

Cuando se intenta compilar el código anterior la misma falla, debido a que (en la línea 7) se realiza una conversión erronea (ClassCastException). Esto se debe a que la variable o1 almacena una referencia a un arreglo de tipo int[][] y se intenta castear el mismo a uno de tipo int[].
Para visualizar la salida de este programa se podría comentar la línea 7, o reemplazarla con el código siguiente:
int[] b2 = (int[]) ((int[][]) o1)[0];
De esta manera lo que se hace es:
  1. Castear el objeto o1 hacia un arreglo de tipo int[][].
  2. Tomar el primer arreglo simple dentro del arreglo multidimensional (o1).
  3. Realizar una conversión explícita del arreglo al tipo int[] (la cuál es redudante, ya que se haría implicitamente).
  4. Finalmente se asigna a la variable de referencia b2 el valor de referencia del arreglo simple.

EJEMPLO 3 (Ver código original)

01. public class Bridge {
02. public enum Suits {
03. CLUBS(20), DIAMONDS(20), HEARTS(30), SPADES(30),
04. NOTRUMP(40) {
05. public int getValue(int bid) {
06. return ((bid-1)*30) + 40;
07. }
08. };
09. Suits(int points) {
10. this.points = points;
11. }
12. private int points;
13. public int getValue(int bid) {
14. return points * bid;
15. }
16. }
17. public static void main(String[] args) {
18. System.out.println(Suits.NOTRUMP.getValue(3));
19. System.out.println(Suits.SPADES + " " + Suits.SPADES.points);
20. System.out.println(Suits.values());
21. }
22. }
/*
SALIDA:
100
SPADES 30
[LBridge$Suits;@addbf1
*/

El ejemplo de código anterior tiene tres líneas de salida:
  1. El valor NOTRUMP del enum Suits tiene sobreescrito el método getValue(int bird). Es por este motivo que cuando se invoca el método getValue() no se invoca la versión orginal.
  2. La línea Suits.SPADES devuelve el valor de la constante como un String.
  3. El método values() sobre el enum Suits devuelve una referencia a un arreglo, es por esto que se muestra el valor de referencia en vez de mostrar los valores del enum.

EJEMPLO 4 (Ver código original)

01. public class Ouch {
02. static int ouch = 7;
03. public static void main(String[] args) {
04. new Ouch().go(ouch);
05. System.out.print(" " + ouch);
06. }
07. void go(int ouch) {
08. ouch++;
09. for(int ouch = 3; ouch < 6; ouch++)
10. ;
11. System.out.print(" " + ouch);
12. }
13. }
/*
Ouch.java:9: ouch is already defined in go(int)
for(int ouch = 3; ouch < 6; ouch++)
^
1 error
*/

Este error se genera debido a que se esta definiendo una variable con un nombre que ya existe para ese ámbito. Es decir, el nombre de la variable del parámetro del método go() es ouch, el error se genera al utilizar este mismo nombre dentro del bloque for (dentro del mismo método o mismo ámbito).

EJEMPLO 5 (Ver código original)

01. public class Bertha {
02. static String s = "";
03. public static void main(String[] args) {
04. int x = 4;
05. Boolean y = true;
06. short[] sa = {1, 2, 3};
07. doStuff(x, y);
08. doStuff(x);
09. doStuff(sa, sa);
10. System.out.println(s);
11. }
12. static void doStuff(Object o) { s += "1"; }
13. static void doStuff(Object... o) { s += "2"; }
14. static void doStuff(Integer... i) { s += "3"; }
15. static void doStuff(Long l) { s += "4"; }
16. }
/*
SALIDA:
212
*/

La salida del código de ejemplo anterior se genera a partir de tres invocaciones al método doStuff() con distintos parámetros:

'2': Resultado de la invocación de la línea 7. El método se invoca con un valor de tipo int y uno Boolean.
doStuff(Object... o): Para el argumento de tipo int se hace un autoboxing hacia el tipo Integer. Como Integer y Boolean cumplen la relación ES-UN con el tipo Object, esta es la versión que se ejecuta.
doStuff(Integer... i): Este método no se invoca debido a que el tipo Boolean NO ES-UN Integer.

'1': Resultado de la invocación de la línea 8. El método se invoca con un valor de tipo int.
doStuff(Object o): Para el argumento de tipo int se hace un autoboxing hacia el tipo Integer. Como Integer cumple la relación ES-UN con el tipo Object, esta es la versión que se ejecuta.
doStuff(Long l): Este método no se invoca debido a que el tipo Integer no cumple la relación ES-UN con el tipo Long.

'2': Resultado de la invocación de la línea 9. El método se invoca con dos argumentos de tipo short[].
doStuff(Object... o): Ambos argumentos son arreglos (es decir, objetos), los mismos se generalizan al tipo Object.
doStuff(Integer... i): Este método no se invoca debido a que el tipo short[] no cumple la relación ES-UN con el tipo Integer.

Asignaciones

A continuación dejo el resumen del tercer capítulo (Assignments) del libro "Sun Certified Programmer for Java 6 Study Guide".

Stack y Heap:
  • Las variables locales (variables declaradas dentro de los métodos) se almacenan en el stack.
  • Los objetos y sus variables de instancia se almacenan en el heap.

Literales y conversión (casting) de tipos primitivos (Objetivo 1.3):
  • Los literales enteros pueden ser decimales, octales o hexadecimales.
  • Los literales octales comienzan siempre con un cero.
  • Los literales hexadecimales comienzan siempre con 0x ó 0X.
  • Los literales para los tipos long debe terminar con la letra L o l.
  • Los literales float deben terminar en F o f, los double con el dígito D, d o sin ningún sufijo (ya que son double por defecto).
  • Los literales boolean son true y false.
  • Los literales para el tipo char son un simple carácter dentro de comillas 'q' o el valor Unicode de una letra '\u004E' (la letra 'N').

Ámbito (Objetivos 1.3 y 7.6):
  • El ámbito se refiere al tiempo de vida de una variable.
  • Existen cuatro tipos básicos de ámbito:
    - Variables estáticas viven durante el tiempo que sus clases se encuentran cargadas en la JVM.
    - Variables de instancia viven durante el tiempo que viven sus objetos.
    - Variables locales viven durante el tiempo en que su método es ejecutado, si su método invoca otro método, estas variables son temporalmente inaccesibles.
    - Variables de bloque (for, if, while) viven hasta que el bloque de código se completa.

Asignaciones básicas (Objetivos 1.3 y 7.6):
  • Los literales de números enteros son implícitamente del tipo int.
  • Las expresiones y operaciones con valores enteros (de cualquier tipo entero) siempre retornan un valor de tipo int.
  • Los números de punto flotante son implícitamente del tipo double (64 bits).
  • La reducción de un tipo primitivo implica el truncamiento de los bits de orden más altos (bits de más a la izquierda).
  • Las asignaciones compuestas (+= ó -=), realizan una conversión automática.
  • Una variable de referencia almacena los bits que son utilizados para apuntar a un objeto.
  • Las variables de referencia pueden apuntar a subclases del tipo declarado, pero no a una superclase.
  • Cuando se crea un objeto nuevo, (Button b = new Button();) suceden tres cosas:
    - Se crea el nombre de una variable de referencia (b) de un tipo (Button).
    - Se crea una nueva instancia de una clase (de tipo Button).
    - Se asigna el objeto instanciado (de tipo Button) a una variable de referencia (b).

Utilización de variables o elementos (de un arreglo) que no están inicializados y tampoco asignados (Objetivos 1.3 y 7.6):
  • Cuando un arreglo de objetos esta inicializado, los objetos que lo componen no se instancian automáticamente, pero todas las referencias poseen el valor por defecto null.
  • Cuando un arreglo de tipos primitivos se inicializa, se asignan a sus elementos los correspondientes valores por defecto.
  • Las variables de instancia siempre se inicializan con su valor por defecto.
  • Las variables locales (también denominadas automáticas o de métodos) nunca se inicializan por defecto. Si se intentan utilizar antes de ser inicializadas, se obtendrá un error de compilación.

Pasando variables hacia métodos (Objetivo 7.3):
  • Los métodos pueden tomar primitivas y/o referencias a objetos como argumentos.
  • Los argumentos de un método siempre son copias.
  • Los argumentos de un método nunca son objetos, si no referencias a estos.
  • Un argumento de tipo primitivo es una copia (desenlazada de la variable original) del valor de otra.
  • Un argumento de referencia es una copia del valor de referencia de otra variable.
  • El ocultamiento (o shadowing) ocurre cuando dos variables con diferente ámbito comparten el mismo nombre.

Declaración de arreglos, construcción e inicialización (Objetivo 1.3):
  • Los arreglos pueden contener primitivas u objetos, pero el arreglo en sí mismo es siempre un objeto.
  • Cuando se declara un arreglo, los corchetes pueden ir a la izquierda o derecha del nombre.
  • No se puede incluir el tamaño del arreglo, en la declaración del mismo.
  • Siempre se debe incluir el tamaño de un arreglo cuando se instancia (utilizando la palabra clave new), salvo que se esté creando un arreglo anónimo.
  • Los elementos de un arreglo de objetos no son instanciados automáticamente (contiene todas sus referencias en null), aunque un arreglo de tipo primitivo si proporciona los valores por defecto a sus elementos.
  • La excepción NullPointerException es lanzada si se intenta utilizar un elemento del arreglo que aún no haya sido asignado.
  • Los arreglos son indexados comenzando desde el valor cero.
  • La excepción ArrayIndexOutOfBoundsException es lanzada si se utiliza un valor de índice incorrecto.
  • Los arreglos poseen la variable length la cuál contiene el número de elementos del mismo.
  • El último índice al que se puede acceder es siempre uno menos que la cantidad de elementos del arreglo.
  • Los arreglos multidimensionales son arreglos de arreglos.
  • Las dimensiones en un arreglo multidimensional pueden tener diferentes tamaños.
  • Un arreglo de tipo primitivo puede aceptar cualquier valor que pueda ser promovido implícitamente al tipo del arreglo. (Por ejemplo un valor de tipo byte puede almacenarse en un arreglo de tipo int).
  • Un arreglo de objetos puede contener cualquier objeto que cumpla la relación ES-UN (o instanceof) con el tipo del arreglo. (Por ejemplo si Horse extiende de Animal, un arreglo de tipo Animal puede almacenar un objeto Horse).
  • Si se asigna un arreglo a una referencia ya declarada, el nuevo arreglo debe ser de la misma dimensión de la referencia del arreglo anterior.
  • Se puede asignar un arreglo de un tipo, a una referencia ya declarada de un arreglo de un supertipo. (Por ejemplo, si Honda extiende de Car, un arreglo de objetos Honda puede ser asignado a un arreglo declarado de tipo Car).

Bloques de inicialización (Objetivo 1.3 y 7.6):
  • Los bloques de inicialización estática se ejecutan solo una vez, cuando la declaración de la clase es cargada.
  • Los bloques de inicialización de instancia se ejecutan cada vez que se crea un nuevo objeto. Se ejecutan luego de los constructores de las clases padres y antes del constructor de la clase actual.
  • Si en una clase existen múltiples bloques de inicialización, estos se ejecutan en el orden en el que aparecen en el código fuente.

Utilizando envoltorios (Wrappers) (Objetivo 3.1):
  • Las clases envoltorio (wrapper) están correlacionadas con los tipos primitivos.
  • Los wrappers tienen dos funciones principales:
    - Envolver a los tipos primitivos, para que puedan ser manejados como objetos.
    - Para proveer métodos de utilidad para los tipos primitivos (generalmente conversiones).
  • Las tres familias de métodos más importantes son:
    - xxxValues(): No toma argumentos, devuelve el primitivo.
    - parseXxx(): Toma un String, retorna el primitivo, y puede lanzar la excepción NumberFormatException.
    - valueOf(): Toma un String, retorna el objeto envoltorio, y puede lanzar la excepción NumberFormatException.
  • Los constructores de las clases envoltorio pueden tomar como argumento un tipo String o un primitivo, salvo Character, el cual solo puede tomar el tipo primitivo char.
  • El parámetro opcional Radix se refiere a las bases de los números (por defecto es decimal = 10), pueden ser octal = 8, hexadecimal = 16, etc.

Boxing (Objetivo 3.1):
  • El mecanismo boxing permite convertir tipos primitivos a envoltorios y envoltorios a primitivos automáticamente.
  • Para los tipos de envoltorio Boolean, Byte, Character, Short e Integer el operador == sirve para comparar sus valores. Esto se debe a que se realizan las conversiones automaticamente a los tipos primitivos para realizar las comparaciones.
  • Para los demás tipos de envoltorios (Long, Float y Double) el operador == se utiliza para comparar las referencias a objetos. Para comparar sus valores, se debe utilizar el método equals().

Sobrecarga avanzada (Objetivos 1.5 y 5.4):
  • "Widening conversion" es el mecanismo de almacenar un dato pequeño en un contenedor más grande.
  • Para métodos sobrecargados utilizar el mecanismo widening sobre primitivos se traduce en invocar el método con argumento “más chico”.
  • Utilizados individualmente, tanto el mecanismo boxing como el var-arg son compatibles con la sobrecarga.
  • No se puede utilizar el mecanismo widening de un tipo wrapper a otro (estos son objetos y no se cumple con la relación ES-UN).
  • No se puede utilizar el mecanismo widening y luego el boxing. (Un valor de tipo int no se puede convertir en un Long).
  • Se puede utilizar el mecanismo boxing y luego el widening. (Un valor de tipo int se puede convertir en un Object, a través del tipo Integer).
  • Se puede combinar el mecanismo var-arg tanto con widening como con boxing.
  • Cuando se invocan métodos sobrecargados ciertos mecanismos se sobreponen ante otros:
    - Widening (Ver ejemplo)
    - Boxing (Ver ejemplo)
    - Var-arg (Ver ejemplo)

Garbage Collection (Objetivo 7.4):
  • Provee una administración automática de la memoria.
  • El propósito del GC es eliminar los objetos que no pueden ser alcanzados (que ninguna variable de referencia los apunta).
  • La JVM es la que decide cuando se ejecutará el GC, el programador solo puede sugerirlo.
  • No se puede conocer el algoritmo del GC.
  • Los objetos deben ser considerados ilegibles antes de que el GC pueda eliminarlos.
  • Un objeto es ilegible cuando ningún hilo vivo puede alcanzarlo.
  • Para alcanzar un objeto, debe existir un hilo vivo que contenga una referencia al objeto.
  • Si existen objetos ilegibles pero con referencias cruzadas entre ellos, estos pueden ser eliminados por el GC. (Island of objects).
  • La clase Object tiene un método finalize().
  • El método finalize() se ejecuta solo una vez, y esto se da antes de que el GC elimine el objeto.
  • El mecanismo GC no garantiza que el método finalize() se ejecutará.

Ejemplos de código - Orientación a Objetos

A continuación dejo unos ejemplos de código con explicaciones. Estos ejemplos surgieron a partir de la realización de los ejercicios del capítulo 2 (Object Orientation) del libro "Sun Certified Programmer for Java 6 Study Guide".
Para ver el post con el resumen de dicho capítulo haz click aquí.
Cada ejemplo tiene un link para visualizar el código original desde el proyecto (SVN) google.

EJEMPLO 1 (Ver código original)

01. class Dog {
02. public void bark() {
03. System.out.print("woof ");
04. }
05. }
06.
07. class Hound extends Dog {
08. public void sniff() {
09. System.out.print("sniff ");
10. }
11. public void bark() {
12. System.out.print("how1 ");
13. }
14. }
15.
16. public class DogShow {
17. public static void main(String[] args) {
18. new DogShow().go();
19. }
20. void go() {
21. new Hound().bark();
22. ((Dog) new Hound()).bark();
23. ((Dog) new Hound()).sniff();
24. }
25. }
/*
DogShow.java:24: cannot find symbol
symbol : method sniff()
location: class Dog
((Dog) new Hound()).sniff();
^
1 error
*/


El casteo explícito (línea 23) provoca que el compilador tome la expresión ((Dog) new Hound()) como de tipo Dog. Como la clase Dog no contiene un método sniff(), se genera el error de compilación "cannot find symbol".
Si no se haría la generalización al tipo Dog, o se agregará una especialización al tipo Hound como la siguiente: ((Hound) ((Dog) new Hound())).sniff(); el programa compilaría y se ejecutaría correctamente.
Por otro lado, al comentar la línea 23 de este ejemplo, la salida sería la siguiente: how1 how1. Esto demuestra que más allá de que se realice la generalización de la línea 22, se invoca la versión del método bark() de la clase Hound. Esto se debe a que la selección de las versiones de los distintos métodos sobreescritos, se hace en tiempo de ejecución, y depende del tipo de objeto instanciado.
En conclusión llegamos a que el código original de este ejemplo, en tiempo de ejecución funcionaría correctamente (porque se invocaría al método sniff() de un objeto Hound). Pero como el compilador no encuentra la declaración del método sniff() en la clase padre,
genera un error de compilación. (Para comprobar lo dicho anteriormente se podría declarar un método sniff() con cualquier cuerpo en la clase Dog, el cuál en tiempo de ejcución nunca sería invocado por este ejemplo.)

01. class Dog {
02. public void bark() {
03. System.out.print("woof ");
04. }
05. public void sniff() {
06. System.out.print("sniff Dog");
07. }
08. }



EJEMPLO 2 (Ver código original)

01. public class Redwood extends Tree {
02. public static void main(String[] args) {
03. new Redwood().go();
04. }
05. void go() {
06. go2(new Tree(), new Redwood());
07. go2((Redwood) new Tree(), new Redwood());
08. }
09. void go2(Tree t1, Redwood r1) {
10. Redwood r2 = (Redwood)t1;
11. Tree t2 = (Tree)r1;
12. }
13. }
14. class Tree {}
/*
SALIDA:
Exception in thread "main" java.lang.ClassCastException: Tree cannot be cast to
Redwood
at Redwood.go2(Redwood.java:10)
at Redwood.go(Redwood.java:6)
at Redwood.main(Redwood.java:3)
*/

Si comentáramos la línea 10 (la cuál provoca la excepción ClassCastException en tiempo de ejecución) y luego ejecutaríamos el programa, obtendríamos la siguiente SALIDA:
Exception in thread "main" java.lang.ClassCastException: Tree cannot be cast to
Redwood
at Redwood.go(Redwood.java:7)
at Redwood.main(Redwood.java:3)

Esto demuestra que el casteo (o especialización) de un objeto Tree hacia Redwood generá una excepción en tiempo de ejecución.
Entonces, se puede concluir que NO se puede realizar una especialización de un tipo base a un subtipo. Es decir, si tenemos una variable de referencia a un subtipo e intentamos asignarle un supertipo obtendremos un error (en este caso de compilación "incompatible types").
Lo mismo sucedería (como lo demuestra este ejemplo) al castear un supertipo hacia un subtipo (pero en este caso es un error un tiempo de ejecución).

EJEMPLO 3 (Ver código original)

01. class A {}
02. class B extends A {}
03. public class ComingThru {
04. static String s = "-";
05. public static void main(String[] args) {
06. A[] aa = new A[2];
07. B[] ba = new B[2];
08. sifter(aa);
09. sifter(ba);
10. sifter(7);
11. System.out.println(s);
12. }
13. static void sifter(A[]... a2) { s += "1"; }
14. static void sifter(B[]... b1) { s += "2"; }
15. static void sifter(B[] b1) { s += "3"; }
16. static void sifter(Object o) { s += "4"; }
17. }
/*
SALIDA:
-434
*/

En general, los métodos sobrecargados con argumentos var-arg son elegidos últimos (para ser ejecutados). Cabe destacar que los arrays son objetos, por este motivo la invocación de la línea 8 ejecuta la versión del método con el parámetro Object.
Entonces se podría decir que ciertos métodos tienen mayores posibilidades de ser ejecutados:
  1. Parámetros con el tipo idéntico (como el caso de la invocación de la línea 9).
  2. Parámetros con el supertipo u Object (como el caso de las invocaciones de las líneas 8 y 10).
  3. Por último si los argumentos no concuerdan con ningún otro método, se ejecutan los métodos con parámetros var-arg.

Orientación a Objetos

A continuación dejo el resumen del segundo capítulo (Object Orientation) del libro "Sun Certified Programmer for Java 6 Study Guide".

Encapsulación, ES-UN, TIENE-UN (Objetivo 5.1):
  • La encapsulación ayuda a ocultar la implementación detrás de una interfaz (API).
  • El código encapsulado tiene dos características:
    - Las variables de instancia son protegidas (generalmente con el modificador de acceso private).
    - Los métodos getter y setter proveen acceso a las variables de instancia.
  • ES-UN se relaciona con la herencia o implementación.
  • ES-UN se expresa mediante la palabra clave extends.
  • ES-UN, “hereda de”, y “es un subtipo de” son expresiones equivalentes.
  • TIENE-UN significa que una clase “tiene una” instancia de otra clase o una de su mismo tipo.

Herencia (Objetivo 5.5):
  • La herencia permite a una clase ser una subclase de una superclase, y por eso se heredan las variables y métodos (public y protected) de la superclase.
  • La herencia es el concepto clave en el que se basan los conceptos ES-UN, polimorfismo, sobreescritura, sobrecarga, y conversión de tipos (casting).
  • Todas las clases son subclases del tipo Object, y también heredan sus métodos.

Polimorfismo (Objetivo 5.2):
  • Polimorfismo significa “muchas formas”.
  • Una variable de referencia es siempre (e inalterable) de un solo tipo, pero puede referirse a un subtipo de objeto.
  • Un objeto puede ser referenciado por una variable de diferentes tipos (su mismo tipo, su subtipo, o de los tipos de interfaces que implemente).
  • El tipo de la variable de referencia (no el tipo del objeto), determina cuales pueden ser los métodos invocados.
  • Las invocaciones a métodos polimórficos se realizan solo a los métodos de instancia sobreescritos.

Sobreescritura y sobrecarga (Objetivo 1.5 y 5.4):
  • Los métodos pueden ser sobreescritos o sobrecargados; los constructores pueden ser sobrecargados pero no sobreescritos.
  • Los métodos abstractos deben ser sobreescritos por la primera subclase concreta (no abstracta).
  • Con respecto al método que sobreescribe, el método sobreescrito:
    - Debe poseer la misma lista de argumentos.
    - Debe poseer el mismo tipo de retorno, excepto a partir de Java 5, en donde el tipo de retorno puede ser una subclase (a esto se lo denomina como retorno covariante).
    - No debe poseer un modificador de acceso más restrictivo.
    - Puede poseer un modificador de acceso menos restrictivo.
    - No debe lanzar nuevas o mayores (en relación al árbol de herencia) excepciones comprobadas.
    - Puede lanzar menos o menores (en relación al árbol de herencia) excepciones comprobadas, o cualquier excepción no comprobada.
    - Los métodos finales no pueden ser sobreescritos.
  • Solo los métodos heredados pueden ser sobreescritos, y se debe recordar que los métodos privados no son heredados.
  • Las subclases deben usar el operador super.nombreDeMetodo(), para invocar las versiones de la superclase del método sobreescrito.
  • Sobrecarga significa reutilizar el nombre de un método, pero con diferentes argumentos.
  • Los métodos sobrecargados:
    - Deben poseer listas de argumentos diferentes.
    - Pueden poseer diferentes tipos de retorno, si las listas de argumentos son diferentes.
    - Pueden poseer diferentes modificadores de acceso.
    - Pueden lanzar diferentes excepciones.
  • Los métodos de una superclase pueden ser sobrecargados en una subclase.
  • El polimorfismo se encuentra en la sobreescritura, no en la sobrecarga.
  • El tipo de un objeto (no el tipo de la variable de referencia), determina que método sobreescrito es utilizado en tiempo de ejecución.
  • El tipo de la referencia determina que método sobrecargado se utilizará en tiempo de complicación.

Conversión de variables de referencia (Objetivo 5.2):
  • Existen dos tipos de conversión de variables de referencia: especificación (downcasting) y generalización (upcasting).
  • Downcasting: Con una referencia a un supertipo de objeto, se puede realizar una conversión explícita a un subtipo, para acceder a los miembros de ese subtipo.
  • Upcasting: Con una referencia a un subtipo de objeto, se puede acceder explícita o implícitamente a los miembros de la clase base.

Implementando una interface (Objetivo 1.2):
  • Cuando se implementa una interface, se está cumpliendo con su contrato.
  • Cuando se implementa una interface, lo que se hace es sobreescribir todos los métodos definidos en la misma.
  • Una clase puede implementar muchas interfaces.

Tipos de retorno (Objetivo 1.5):
  • Los métodos sobrecargados pueden cambiar su tipo de retorno; los métodos sobreescritos no, excepto en el caso de que sean tipos de retornos covariantes.
  • Los tipos de retorno de referencias a objetos pueden devolver el valor null.
  • Un arreglo es un tipo de retorno válida, siempre y cuando se declare y se retorne.
  • Los métodos con tipos primitivos de retorno, pueden realizar conversiones implícitas para devolver sus valores.
  • Los métodos con retorno void, no pueden devolver nada. Es decir, se puede utilizar la palabra clave return para finalizar la ejecución del método, pero no se puede devolver ningún tipo de valor, salvo void.
  • Los métodos con referencias a objetos como tipo de retorno, pueden devolver subtipos.
  • Los métodos con una interface como tipo de retorno, pueden devolver cualquier instancia de una clase que la implemente.

Constructores e instanciación (Objetivo 1.6 y 5.4):
  • Un constructor es invocado siempre que un objeto es creado.
  • Para instanciar un objeto, cada superclase en el árbol de herencia posee un constructor que es invocado.
  • Cada clase, incluso las clases abstractas, poseen al menos un constructor.
  • Los constructores deben poseer el mismo nombre que la clase.
  • Los constructores no poseen un tipo de retorno. Si posee un tipo de retorno no es un constructor, sino un simple método con el mismo nombre que la clase.
  • La ejecución típica de los constructores se realiza de la siguiente manera:
    - El constructor invoca al constructor de su superclase, el cuál invoca al constructor de su superclase, y sigue así a través de todo el árbol de herencia hasta alcanzar el constructor de la clase Object.
    - Se ejecuta el constructor de la clase Object y luego se continúa con la ejecucióncorrespondiente al constructor de la primera subclase (de Object). Se sigue el mismo proceso “hacia abajo” hasta finalizar la ejecución del constructor que desencadeno las distintas creaciones.
  • Los constructores pueden utilizar cualquier modificador de acceso.
  • El compilador crea un constructor por default, si no se crea ningún constructor explícito en la clase.
  • El constructor por default no posee argumentos, y posee una llamada al constructor sin argumentos de su clase padre super().
  • La primera línea de todos los constructores debe ser this() (si es un constructor sobrecargado) o super().
  • El compilador agregará una llamada a super() (implícita) a menos que se especifique una llamada a this() o super().
  • Los miembros de instancia son accesibles solo después de la ejecución del constructor del supertipo.
  • Las clases abstractas poseen constructores, los cuales son invocados cuando una subclase concreta se instancia.
  • Las interfaces no poseen constructores.
  • Si una superclase no posee un constructor sin argumentos, es aconsejable crear un constructor sin argumentos el cuál invoque a alguno de los constructores existentes pasando parámetros por defecto.
  • Los constructores nunca son heredados, y por lo tanto no pueden ser sobreescritos.
  • Un constructor solo puede ser invocado directamente por otro constructor, a través de las palabras clave this() o super().
  • Características de la llamada this():
    - Puede estar solo en la primera línea de un constructor.
    - La lista de argumentos determina que constructor sobrecargado es invocado.
  • Los constructores pueden invocar otros constructores, y así sucesivamente. Pero tarde o temprano se deberá invocar a super(), ya que si no se arrojará una excepción relacionada a la pila.
  • Las invocaciones a this() y super() no pueden estar en un mismo constructor. Se puede tener uno u otro, pero nunca ambos.

Estáticos (Objetivo 1.3):
  • Los métodos estáticos son utilizados para implementar comportamientos que no están afectados por el estado de ninguna instancia.
  • Las variables estáticas se utilizan para almacenar información que es específica de la clase. Existe solo una copia de las variables estáticas.
  • Todos los miembros estáticos pertenecen a la clase, no a una instancia.
  • Un método estático no puede acceder a una variable de instancia directamente.
  • Para acceder a los miembros estáticos se utiliza el operador punto (.). Se debe recordar que la utilización de una instancia para acceder a un miembro estático es solo un truco, ya que el compilador sustituye la variable de referencia por el nombre de la clase.
  • Los métodos estáticos no pueden ser sobreescritos, pero si pueden ser redefinidos.

Acoplamiento y cohesión (Objetivo 5.1):
  • El acoplamiento se refiere al grado en que una clase conoce o usa los miembros de otra clase.
  • Bajo acoplamiento es el estado deseado para tener clases que estén bien encapsuladas, minimizadas las referencias entre ellas, y se limitan al uso de las API’s.
  • Alto acoplamiento es el estado indeseado de tener clases que no respetan las reglas de bajo acoplamiento.
  • La cohesión se refiere al grado en que una clase, cumple un rol bien definido o tiene una responsabilidad específica.
  • Alta cohesión es el estado deseado de una clase que posee un rol bien definido.
  • Baja cohesión es el estado indeseado de una clase que posee muchos roles y demasiadas responsabilidades.

Declaraciones y Control de acceso

A continuación dejo un link con los objetivos del exámen de certificación SCJP.

Actualmente estoy leyendo el libro "Sun Certified Programmer for Java 6 Study Guide" (el cuál puede ser descargado desde el proyecto de google o haciendo click aquí). Hoy termine de leer el primer capítulo el cuál tiene el mismo nombre que esta entrada (Declarations and Access Control). Lo bueno de este libro es que al final de cada capítulo hay un resumen con todos los items importantes a tener en cuenta a la hora de rendir el exámen. Esta sección se llama "TWO-MINUTE DRILL". También contiene un mini exámen para autoevaluarse con los tópicos tratados en el capítulo.
A continuación dejo el resumen del capítulo (traducido por mí) del libro. Espero que les sea de utilidad.

Identificadores (Objetivo 1.3):
  • Los identificadores pueden comenzar con una letra, un guión bajo o un carácter de moneda.
  • Luego del primer carácter, los identificadores pueden incluir dígitos.
  • Pueden ser de cualquier longitud.
  • La especificación JavaBeans establece que los métodos deben definirse utilizando camelCase, y dependiendo del propósito del método, deben comenzar con get, set, is, add o remove (estos últimos dos deben terminar con el sufijo Listener).

Reglas de declaración (Objetivo 1.1):
  • Un archivo de código fuente solo puede contener una clase pública.
  • Si el archivo contiene una clase pública, el nombre del mismo debe ser igual al nombre de esa clase pública.
  • Un archivo solo puede contener una declaración de package, pero múltiples declaraciones de imports.
  • Si existe la declaración package, esta debe estar en la primera línea (que no sea un comentario) del archivo.
  • Si existe la declaración de imports, deben estar luego de la declaración package y antes de la declaración de la clase.
  • Si no existe la declaración package, la declaración import debe estar en la primera línea (que no sea un comentario) del archivo.
  • Las declaraciones package y import, aplican a todas las clases del archivo.
  • Un archivo puede contener más de una clase no pública.
  • Los archivos sin ninguna clase pública no tienen restricciones de nombre.

Modificadores de acceso a clase (Objetivo 1.1):
  • Existen tres modificadores de acceso: public, protected y private.
  • Existen cuatro niveles de acceso: public, protected, default y private.
  • Las clases solo pueden ser declaradas con acceso public o default.
  • Una clase con acceso default solo puede ser vista por las clases dentro del mismo paquete.
  • Una clase con acceso public puede ser vista por todas las clases de todos los paquetes.
  • La visibilidad de clases consiste en si el código de una clase puede:
    - Crear una instancia de otra clase.
    - Extender otra clase (a través de la herencia).
    - Acceder a métodos y variables de otra clase.

Modificadores de clase (Sin acceso) (Objetivo 1.2):
  • Las clases también pueden ser modificadas con final, abstract o strictfp.
  • Una clase no puede ser final y abstract.
  • Una clase final no puede ser extendida.
  • Una clase abstract no puede ser instanciada.
  • Un método abstracto dentro de una clase significa que la clase entera debe ser abstract.
  • Una clase abstracta puede tener métodos abstractos y no abstractos.
  • La primera clase concreta que extienda de una clase abstracta debe implementar todos los métodos abstractos.

Implementación de interfaces (Objetivo 1.2):
  • Las interfaces son contratos sobre lo que puede hacer una clase, pero no dicen nada acerca de cómo deben realizarlo.
  • Las interfaces pueden ser implementadas por cualquier clase, de cualquier árbol de herencia.
  • Una interface es como una clase abstracta al cien por ciento. Y es implícitamente abstracta más allá de que contenga o no el modificador abstract.
  • Una interface solo puede contener métodos abstractos.
  • Los métodos de una interface son por defecto públicos y abstractos, la declaración implícita de estos modificadores es opcional.
  • Las interfaces pueden contener constantes, las cuales siempre son public, static y final (la declaración implícita puede hacerse en cualquier orden).
  • Una clase concreta que implemente una interface tiene las siguientes propiedades:
    - Provee implementaciones concretas para los métodos abstractos de la interface.
    - Debe seguir todas las reglas de sobreescritura de los métodos que implementa.
    - No debe añadir ninguna nueva excepción a los métodos implementados. (Solo las excepciones declaradas en la interface pueden ser lanzadas desde el método.)
    - Debe mantener la misma signatura y tipo de retorno (también se permiten los tipos de retorno covariantes) de los métodos que implementa.
  • Una clase que implemente una interface también puede ser abstract.
  • Una clase abstracta que implemente una interface no necesita implementar los métodos de la misma (pero sí lo debe hacer la primer clase no abstracta que la extienda).
  • Una clase puede extender solo de una sola clase (no está permitida la herencia múltiple), pero si puede implementar muchas interfaces.
  • Las interfaces pueden extender de una o más interfaces.
  • Las interfaces no pueden extender una clase, o implementar una clase o interface.

Modificadores de acceso de miembros (Objetivo 1.3 y 1.4):
  • Los miembros son los métodos y variables de instancia.
  • Los miembros pueden utilizar los cuatro niveles de acceso: public, protected, default y private.
  • Los accesos a los miembros se pueden realizar de dos formas:
    - Código en una clase que puede acceder a un miembro de otra clase.
    - Una subclase que puede heredar un miembro de una superclase.
  • Si no se puede acceder a una clase, tampoco se podrá acceder a sus miembros.
  • Determinar la visibilidad de una clase, antes de determinar la visibilidad de sus miembros.
  • Los miembros públicos pueden accederse desde todas las demás clases, incluso en otros paquetes.
  • Si la superclase contiene un miembro público, la subclase lo hereda.
  • Los miembros accedidos sin el operador punto (.) deben pertenecer a la misma clase.
  • La palabra clave this siempre se refiere al objeto que se está ejecutando. Dentro de la misma clase this.unMetodo() es lo mismo que invocar unMetodo().
  • Los miembros privados solo pueden accederse desde el código de la misma clase.
  • Los miembros privados no son visibles para las subclases, o sea no pueden ser heredados.
  • Los miembros default y protected difieren solo en lo relacionado a las subclases:
    - Miembros default pueden ser accedidos solo por las clases pertenecientes al mismo paquete.
    - Miembros protected pueden ser accedidos por otras clases pertenecientes al mismo paquete, y además por las subclases pertenecientes a otros clases.
    - Para las subclases fuera del paquete, los miembros protected solo pueden ser accedidos a través de la herencia. Una subclase fuera del paquete no puede acceder a los miembros protegidos a través de una referencia a una instancia de la superclase.

Variables locales (Objetivo 1.3):
  • Las declaraciones de variables locales no pueden tener modificadores de acceso.
  • El único modificador que se puede aplicar a las variables locales es final.
  • Las variables locales no poseen valores por default (a diferencia de las variables de instancia), por lo que deben ser inicializadas antes de utilizarlas.

Otros modificadores de miembros (Objetivo 1.3):
  • Los métodos final no pueden ser sobreescritos por las subclases.
  • Los métodos abstractos son declarados con una signatura, un tipo de retorno, opcionalmente se puede agregar la clausula throws, pero no están implementados.
  • Los métodos abstractos terminan con punto y coma, no con llaves.
  • La primera clase concreta que extienda a una clase abstracta debe implementar todos los métodos abstractos.
  • El modificador synchronized puede ser aplicado solo a métodos y a bloques de código.
  • Los métodos synchronized pueden tener cualquier control de acceso y también pueden ser final.
  • Los métodos abstractos deben ser implementados por las subclases, por este motivo:
    - Los métodos abstract no pueden ser private.
    - Los métodos abstract no pueden ser final.
  • El modificador native solo se puede aplicar a los métodos.
  • El modificador strictfp solo se puede aplicar a las clases y métodos.

Métodos con var-args (Objetivo 1.4):
  • Desde Java 5, los métodos pueden contener un parámetro que acepte desde cero a muchos argumentos, este tipo de parámetro se denomina var-arg.
  • Un parámetro var-arg es declarado con la sintaxis: doStuff(int… x) { }.
  • Un método solo puede contener un parámetro var-arg.
  • Un método con parámetros normales y uno var-arg, el var-arg debe ser el último parámetro.

Declaraciones de variables (Objetivo 1.3):
  • Las variables de instancia pueden:
    - Tener cualquier control de acceso.
    - Ser final o transient.
  • Las variables de instancia no pueden ser abstract, synchronized, native, o strictfp.
  • Es legal declarar una variable local con el mismo nombre que una variable de instancia, esto se llama “Shadowing”.
  • Las variables finales tienen las siguientes propiedades:
    - No pueden ser reinicializadas una vez que ya se le ha asignado un valor.
    - Las referencias finales no pueden apuntar a otro objeto, una vez que un objeto ya ha sido asignado.
    - Las referencias finales deben ser inicializadas antes de completarse la ejecución del constructor.
  • Una referencia final a un objeto, no significa que el objeto no pueda cambiar su estado.
  • El modificador transient es aplicable solo a las variables de instancia.
  • El modificador volatile es aplicable solo a las variables de instancia.

Declaraciones de arreglos “Arrays” (Objetivo 1.3):
  • Los arreglos pueden contener tipos primitivos u objetos, pero el arreglo en sí mismo es siempre un objeto.
  • Cuando se declara un arreglo, los corchetes pueden ir antes o después del nombre de la variable.
  • No es legal incluir el tamaño del arreglo en la declaración del mismo. (Esto se puede hacer en la instanciación del mismo)
  • Un arreglo de objetos de un tipo puede contener cualquier objeto hijo de ese tipo.

Variables y métodos estáticos (Objetivo 1.4):
  • No se relacionan con ninguna instancia de clase.
  • No se necesitan instancias de clases para acceder a los miembros estáticos.
  • Solo existe una copia de las variables estáticas de una clase, y todas las instancias de la clase la comparten.
  • Los métodos estáticos no tienen acceso directo a los miembros no estáticos.

Enumeraciones “Enums” (Objetivo 1.3):
  • Un enum especifica una lista de valores constantes asignados a un tipo.
  • Un enum no es un objeto String, es un objeto de tipo enum.
  • Un enum puede ser declarado dentro o fuera de una clase, pero no en un método.
  • Un enum declarado fuera de una clase no puede ser static, final, abstract, protected o private.
  • Los enums pueden pueden contener constructores, métodos, variables, y cuerpos de clase constantes (constant class bodies).
  • Las constantes enums pueden enviar argumentos al constructor del enum, usando la sintaxis BIG(8), en donde el literal 8 es pasado al constructor como argumento.
  • Los constructores enum pueden tener argumentos y pueden ser sobrecargados.
  • Los constructores de enums no pueden ser invocados directamente. Estos son llamados automáticamente cuando un enum es inicializado.
  • El punto y coma final de la declararión de un enum es opcional.
  • El método MyEnum.values() retorna un arreglo con todos los valores del enum.