Bienvenidos espero les guste la pagina

JAVA3

Paso por Valor

En los métodos Java, los argumentos son pasados por valor. Cuando se le llama, el método recibe el valor de la variable pasada. Cuando el argumento es de un tipo primitivo, pasar por valor significa que el método no puede cambiar el valor. Cuando el argumento es del tipo de referencia, pasar por valor significa que el método no puede cambiar el objeto referenciado, pero si puede invocar a los métodos del objeto y puede modificar las variables accesibles dentro del objeto.

Consideremos esta serie de sentencias Java que intentan recuperar el color actual de un objeto Pen en una aplicación gráfica.

. . .

int r = -1, g = -1, b = -1;

pen.getRGBColor(r, g, b);

System.out.println("red = " + r + ", green = " + g + ", blue = " + b);

. . .

 

En el momento que se llama al método getRGBColor(), las variables r, g, y b tienen un valor de -1. El llamador espera que el método getRGBColor() le devuelva los valores de rojo, verde y azul para el color actual en las variables r, g, y b.

Sin embargo, el sistema Java pasa los valores de las variables(-1) al método getRGBColor(); no una referencia a las variables r, g, y b.

Con esto se podría visualizar la llamada a getRGBColor() de esta forma.

 

getRGBColor(-1, -1, -1)

 

Cuando el control pasa dentro del método getRGBColor(), los  argumentos entran dentro del ámbito (se les asigna espacio) y son inicializados a los valores pasados al método.

 

class Pen {

int valorRojo, valorVerde, valorAzul;

void getRGBColor(int rojo, int verde, int azul) {

// rojo, verde y azul han sido creados y sus valores son -1

. . .

}

}

 

Con esto getRGBColor() obtiene acceso a los valores de r, g, y b del llamador a tavés de sus argumentos rojo, verde, y azul, respectivamente.

El método obtiene su propia copia de los valores para utilizarlos dentro del ámbito del método. Cualquier cambio realizado en estas copias locales no sera reflejado en las variables originales del llamador.

Ahora veremos la implementación de getRGBColor() dentro de la clase Pen que implicaba la firma de método anterior.

 

class Pen {

int valorRojo, valorVerde, valorAzul;

. . .

// Este método no trabaja como se espera

void getRGBColor(int rojo, int verde, int azul) {

rojo = valorRojo;

verde=valorVerde;

azul=valorAzul;

}

}

 

Este método no trabajará como se espera. Cuando el control llega a la sentencia println() en el siguiente fragmento de código, los argumentos rojo, verde y azul de getRGBColor() ya no existen. Por lo tanto las

asignaciones realizadas dentro del método no tendrán efecto; r, g, y b seguiran siendo igual a -1.

. . .

int r = -1, g = -1, b = -1;

pen.getRGBColor(r, g, b);

System.out.println("rojo = " + r + ", verde = " + g + ", azul = " + b);

. . .

El paso de las varibales por valor le ofrece alguna seguridad a los programadores: los métodos no puede modificar de forma no intencionada una variable que está fuera de su ámbito. Sin embargo, alguna vez se querrá que un método modifique alguno de sus argumentos. El metodo getRGBColor() es un caso apropiado. El llamador quiere que el método devuelva tres valores a través de sus argumentos. Sin embargo, el método no puede modificar sus argumentos, y, además, un método sólo puede devolver un valor a través de su valor de retorno. Entonces, ¿cómo puede un método devolver más de un valor, o tener algún efecto (modificar algún valor) fuera de su ámbito?

Para que un método modifique un argumento, debe ser un tipo de  referencia como un objeto o un array. Los objetos y arrays también son pasados por valor, pero el valor de un objeto es una referencia. Entonces el efecto es que los argumentos de tipos de referencia son pasados por referencia. De aquí el nombre. Una referencia a un objeto es la dirección del objeto en la memoria. Ahora, el argumento en el método se refiere a la misma posición de memoria que el llamador.

Reescribamos el método getRGBColor() para que haga lo que  quye se quiere. Primero introduzcamos un nuevo objeto RGBColor, que puede contener los valores de rojo, verde y azul de un color en formato RGB.

 

class RGBColor {

public int rojo, verde, azul;;

}

 

Ahora podemos reescribir getRGBColor() para que acepte un objeto RGBColor como argumento. El método getRGBColor() devuelve el color actual de lápiz, en los valores de las variables miembro rojo, verde y azul de su argumento RGBColor.

 

class Pen {

int valorRojo, valorVerde, valorAzul;

void getRGBColor(RGBColor unColor) {

unColor.rojo = valorRojo;

unColor.verde = valorVerde;

unColor.azul = valorAzul;

}

}

 

Y finalmente, reescribimos la secuencia de llamada.

. . .

RGBColor penColor = new RGBColor();

pen.getRGBColor(penColor);

System.out.println("ojo = " + penColor.rojo + ", verde = " + penColor.verde + ", azul = " + penColor.azul);

. . .

Las modificaciones realizadas al objeto RGBColor dentro del método getRGBColor() afectan al objeto creado en la secuencia de llamada porque los nombres penColor (en la secuencia de llamada) y unColor (en el método getRGBColor()) se refieren al mismo objeto.

 

El Cuerpo de un Método

En el siguiente ejemplo, el cuerpo de método para los métodos estaVacio() y poner() están en negrita.

 

class Stack {

static final int PILA_VACIA = -1;

Object[] elementosPila;

int elementoSuperior = PILA_VACIA;

. . .

boolean estaVacio() {

if (elementoSuperior == PILA_VACIA)

return true;

else

return false;

}

Object poner() {

if (elementoSuperior == PILA_VACIA)

return null;

else {

return elementosPila[elementoSuperior--];

}

}

}

 

Junto a los elementos normales del lenguaje Java, se puede utilizar this en el cuerpo del método para referirse a los miembros del objeto actual.

El objeto actual es el objeto del que uno de cuyos miembros está siendo llamado. También se puede utilizar super para referirse a los miembros de la superclase que el objeto actual haya ocultado mediante la sobreescritura. Un cuerpo de método también puede contener declaraciones de variables que son locales de ese método.

Normalmente, dentro del cuerpo de un método de un objeto se puede referir directamente a las variables miembros del objeto. Sin embargo, algunas veces no se querrá tener ambigüedad sobre el nombre de la variable miembro y uno de los argumentos del método que tengan el mismo nombre.

 

This

 

Por ejemplo, el siguiente constructor de la clase HSBColor inicializa alguna variable miembro de un objeto de acuerdo a los argumentos pasados al constructor. Cada argumento del constructor tiene el mismo nombre que la variable del objeto cuyo valor contiene el argumento.

 

class HSBColor {

int hue, saturacion, brillo;

HSBColor (int luminosidad, int saturacion, int brillo) {

this.luminosidad = luminosidad;

this.saturacion = saturacion;

this.brillo = brillo;

}

}

 

Se debe utilizar this en este constructor para evitar la ambigüedad entre el argumento luminosidad y la variable miembro luminosidad (y así con el resto de los argumentos). Escribir luminosidad = luminosidad; no tendría sentido. Los nombres de argumentos tienen mayor precedencia y ocultan a los nombres de las variables miembro con el mismo nombre. Para referirise a la variable miembro se debe hacer explicitamente a través del objeto actual--this.

También se puede utilizar this para llamar a uno de los métodos del objeto actual. Esto sólo es necesario si existe alguna ambigüedad con el nombre del método y se utiliza para intentar hacer el código más claro.

 

super

 

Si el método oculta una de las variables miembro de la superclase, se puede referir a la variable oculta utilizando super. De igual forma, si el método sobreescribe uno de los métodos de la superclase, se puede llamar al método sobreescrito a través de super.

 

Consideremos esta clase.

class MiClase {

boolean unaVariable;

void unMetodo() {

unaVariable = true;

}

}

y una subclase que oculta unaVariable y sobreescribe unMetodo().

class OtraClase extends MiClase {

boolean unaVariable;

void unMetodo() {

unaVariable = false;

super.unMetodo();

System.out.println(unaVariable);

System.out.println(super.unaVariable);

}

}

 

Primero unMetodo() selecciona unaVariable (una declarada en OtraClase que oculta a la declarada en MiClase) a false. Luego unMetodo() llama a su método sobreescrito con esta sentencia.

 

super.unMetodo();

 

Esto selecciona la versión oculta de unaVariable (la declarada en MiClase) a true.

Luego unMetodo muestra las dos versiones de unaVariable con diferentes valores.

 

False  y  True

 

Variables Locales

Dentro del cuerpo de un método se puede declarar más variables para usarlas dentro del método.

Estas variables son variables locales y viven sólo mientras el control permanezca dentro del método. Este método declara un variable local i y la utiliza para operar sobre los elementos del array.

 

Object encontrarObjetoEnArray(Object o, Object[] arrayDeObjetos) {

int i; // variable local

for (i = 0; i < arrayDeObjetos.length; i++) {

if (arrayDeObjetos[i] == o)

return o;

}

return null;

}

 

Después de que este método retorne, i ya no existirá más.

 

Miembros de la Clase y del Ejemplar

Cuando se declara una variable miembro como unFloat en MiClase.

class MiClase {

float unFloat;

}

declara una variable de ejemplar. Cada vez que se crea un ejemplar de la clase, el sistema crea una copia de todas las variables de ejemplar de la clase.

Las variables de ejemplar están en constraste con las variables de clase (que se declaran utilizando el modificador static). El sistema asigna espacio para las variables de clase una vez por clase, sin importar el número de ejemplares creados de la clase. Todos los objetos creados de esta clase comparten la misma copia de las variables de clase de la clase, se puede acceder a las variables de clase a través de un ejemplar o a través de la propia clase.

Los métodos son similares: una clase puede tener métodos de ejemplar y métodos de clase. Los métodos de ejemplar operan sobre las variables de ejemplar del objeto actual pero también pueden acceder a las variables de clase. Por otro lado, los métodos de clase no pueden acceder a las variables del ejemplar declarados dentro de la clase (a menos que se cree un objeto nuevo y acceda a ellos a través del objeto). Los métodos de clase también pueden ser invocados desde la clase, no se necesita un ejemplar para llamar a los métodos de la clase. Por defecto, a menos que se especifique de otra forma, un miembro declarado dentro de una clase es un miembro del ejemplar. La clase definida abajo tiene una variable de ejemplar -- un entero llamado x -- y dos métodos de ejemplar -- x() y setX() -- que permite que otros objetos pregunten por el valor de x.

 

class UnEnteroLlamadoX {

int x;

public int x() {

return x;

}

public void setX(int newX) {

x = newX;

}

}

 

Cada vez que se ejemplariza un objeto nuevo desde una clase, se obtiene una copia de cada una de las variables de ejemplar de la clase. Estas copias están asociadas con el objeto nuevo. Por eso, cada vez que se ejemplariza un nuevo objeto UnEnteroLlamadoX de la clase, se obtiene una copia de x que está asociada con el nuevo objeto UnEnteroLlamadoX.

Todos los ejemplares de una clase comparten la misma implementación de un método de ejemplar; todos los ejemplares de UnEnteroLlamadoX comparten la misma implementación de x() y setX(). Observa que estos métodos se refieren a la variable de ejemplar del objeto x por su nombre. "Pero, ¿si todos los ejemplares de UnEnteroLlamadoX comparten la misma implementación de x() y setX() esto no es ambigüo?" La respuesta es no. Dentro de un método de ejemplar, el nombre de una variable de ejemplar se refiere a la variable de ejemplar del objeto actual (asumiendo que la variable de ejemplar no está ocultada por un parámetro del método). Ya que, dentro de x() y setX(), x es equivalente a this.x.

Los objetos externos a UnEnteroLlamadoX que deseen acceder a x deben hacerlo a través de un ejemplar particular de UnEnteroLlamadoX. Supongamos que este código estuviera en otro método del objeto. Crea dos objetos diferentes del tipo UnEnteroLlamadoX, y selecciona sus valores de x a diferente valores y luego lo muestra:

. . .

UnEnteroLlamadoX miX = new UnEnteroLlamadoX();

UnEnteroLlamadoX otroX = new UnEnteroLlamadoX();

miX.setX(1);

otroX.x = 2;

System.out.println("miX.x = " + miX.x());

System.out.println("otroX.x = " + otroX.x());

. . .

Observese que el código utilizado en setX() para seleccionar el valor de x para miX pero sólo asignando el valor otroX.x directamente. De otra forma, el código manipula dos copias diferentes de x: una contenida en el objeto miX y la otra en el objeto otroX. La salida producida por este  código es.

 

miX.x = 1

otroX.x = 2

 

mostrando que cada ejemplar de la clase UnEnteroLlamadoX tiene su propia copia de la variable de ejemplar x y que cada x tiene un valor diferente.

Cuando se declara una variable miembro se puede especificar que la variable es una variable de clase en vez de una variable de ejemplar. Similarmente, se puede especificar que un método es un método de clase en vez de un método de ejemplar. El sistema crea una sola copia de una variable de clase la primera vez que encuentra la clase en la que está definida la variable. Todos los ejemplares de esta clase comparten la misma copia de las variables de clase. Los métodos de clase sólo pueden operar con variables de clase -- no pueden acceder a variables de ejemplar definidas en la clase.

Para especificar que una variable miembro es una variable de clase, se utiliza la palabra clave static. Por ejemplo, cambiemos la clase UnEnteroLlamadoX para que su variable x sea ahora una variable de clase.

 

class UnEnteroLlamadoX {

static int x;

public int x() {

return x;

}

public void setX(int newX) {

x = newX;

}

}

 

Ahora veamos el mismo código mostrado anteriormente que crea dos ejemplares de UnEnteroLlamadoX, selecciona sus valores de x, y muestra esta salida diferente.

 

miX.x = 2

otroX.x = 2

 

La salida es diferente porque x ahora es una variable de clase por lo que sólo hay una copia de la variable y es compartida por todos los ejemplares de UnEnteroLlamadoX incluyendo miX y otroX.

Cuando se llama a setX() en cualquier ejemplar, cambia el valor de x para todos los ejemplares de UnEnteroLlamadoX.

Las variables de clase se utilizan para aquellos puntos en los que se necesite una sola copia que debe estar accesible para todos los objetos heredados por la clase en la que la variable fue declarada. Por ejemplo, las variables de clase se utilizan frecuentemente con final para definir constantes (esto es más eficiente en el consumo de memoria, ya que las constantes no pueden cambiar y sólo se necesita una copia).

Similarmente, cuando se declare un método, se puede especificar que el método es un método de clase en vez de un método de ejemplar. Los métodos de clase sólo pueden operar con variables de clase y no pueden

acceder a las variables de ejemplar definidas en la clase.

Para especificar que un método es un método de clase, se utiliza la palabra clave static en la declaración de método. Cambiemos la clase UnEnteroLlamadoX para que su variable miembro x sea de nuevo una variable de ejemplar, y sus dos métodos sean ahora métodos de clase.

 

class UnEnteroLlamadoX {

private int x;

static public int x() {

return x;

}

static public void setX(int newX) {

x = newX;

}

}

 

Cuando se intente compilar esta versión de UnEnteroLlamadoX, se obtendrán errores de compilación.

Esto es porque los métodos de la clase no pueden acceder a variables de ejemplar a menos que el método haya creado un ejemplar de UnEnteroLlamadoX primero y luego acceda a la variable a través de él.

Construyamos de nuevo UnEnteroLlamadoX para hacer que su variable x sea una variable de clase.

 

class UnEnteroLlamadoX {

static private int x;

static public int x() {

return x;

}

static public void setX(int newX) {

x = newX;

}

}

 

Ahora la clase se compilará y el código anterior que crea dos ejemplares de UnEnteroLlamadoX, selecciona sus valores x, y muestra en su salida los valores de x.

 

miX.x = 2

otroX.x = 2

 

De nuevo, cambiar x a través de miX también lo cambia para los otros ejemplares de UnEnteroLlamadoX. Otra diferencia entre miembros del ejemplar y de la clase es que los miembros de la clase son accesibles desde la propia clase. No se necesita ejemplarizar la clase para acceder a los miembros de clase.

Reescribamos el código anterior para acceder a x() y setX() directamente desde la clase UnEnteroLlamadoX.

. . .

UnEnteroLlamadoX.setX(1);

System.out.println("UnEnteroLlamadoX.x = " + UnEnteroLlamadoX.x());

. . .

Observa que ya no se tendrá que crear miX u otroX. Se puede seleccionar x y recuperarlo directamente desde la clase UnEnteroLlamadoX. No se puede hacer esto con miembros del ejemplar. Solo se puede invocar métodos de ejemplar a través de un objeto y sólo puede acceder a las variables de ejemplar desde un objeto.

Se puede acceder a las variables y métodos de clase desde un ejemplar de la clase o desde la clase misma.

 

Controlar el Acceso a los Miembros de la Clase

Uno de los beneficos de las clases es que pueden proteger sus variables y métodos miembros frente al acceso de otros objetos. ¿Por qué es esto importante? Bien, consideremos esto. Se ha escrito una clase que

representa una petición a una base de datos que contiene toda clase de información secreta, es decir, registros de empleados o proyectos secretos de la compañia.

Ciertas informaciones y peticiones contenidas en la clase, las soportadas por los métodos y variables accesibles públicamente en su objeto son correctas para el consumo de cualquier otro objeto del sistema.

Otras peticiones contenidas en la clase son sólo para el uso personal de la clase. Estas otras soportadas por la operación de la clase no deberían ser utilizadas por objetos de otros tipos. Se querría proteger esas variables y métodos personales a nivel del lenguaje y prohibir el acceso desde objetos de otros tipos.

En Java se pueden utilizar los especificadores de acceso para proteger tanto las variables como los métodos de la clase cuando se declaran. El lenguaje Java soporta cuatro niveles de acceso para las variables y

métodos miembros: private, protected, public, y, todavía no especificado, acceso de paquete.

La siguiente tabla le muestra los niveles de acceso pemitidos por cada especificador.

Especificador

clase

subclase

paquete

mundo

private

X

 

 

 

protected

X

X*

X

 

Public

X

X

X

X

Package

X

 

X

 

 

La primera columna indica si la propia clase tiene acceso al miembro definido por el especificador de acceso.

La segunda columna indica si las subclases de la clase (sin importar dentro de que paquete se encuentren estas) tienen acceso a los miembros. La tercera columna indica si las clases del mismo paquete que la clase (sin importar su parentesco) tienen acceso a los miembros. La cuarta columna indica si todas las clases tienen acceso a los miembros.

Observa que la intersección entre protected y subclase tiene un '*' - este caso de acceso particular tiene una explicación en más detalle más adelante.

Echemos un vistazo a cada uno de los niveles de acceso más detalladamente.

 

Private

El nivel de acceso más restringido es private. Un miembro privado es accesible sólo para la clase en la que está definido. Se utiliza este acceso para declarar miembros que sólo deben ser utilizados por la clase. Esto incluye las variables que contienen información que si se accede a ella desde el exterior podría colocar al objeto en un estado de inconsistencia, o los métodos que llamados desde el exterior pueden poner en peligro el estado del objeto o del programa donde se está ejecutando. Los miembros privados son como secretos, nunca deben contarsele a nadie.

Para declarar un miembro privado se utiliza la palabra clave private en su declaración. La clase siguiente contiene una variable miembro y un método privados.

 

class Alpha {

private int soyPrivado;

private void metodoPrivado() {

System.out.println("metodoPrivado");

}

}

 

Los objetos del tipo Alpha pueden inspeccionar y modificar la variable soyPrivado y pueden invocar el método metodoPrivado(), pero los objetos de otros tipos no pueden acceder. Por ejemplo, la clase Beta

definida aquí.

 

class Beta {

void metodoAccesor() {

Alpha a = new Alpha();

a.soyPrivado = 10; // ilegal

a.metodoPrivado(); // ilegal

}

}

 

no puede acceder a la variable soyPrivado ni al método metodoPrivado() de un objeto del tipo Alpha porque Beta no es del tipo Alpha.

Si una clase está intentando acceder a una variable miembro a la que no tiene acceso--el compilador mostrará un mensaje de error.

 

a.iamprivate = 10; // ilegal

 

Y si un programa intenta acceder a un método al que no tiene acceso, generará un error de compilación.

 

a.privateMethod(); // ilegal

 

Protected

El siguiente especificador de nivel de acceso es 'protected' que permite a la propia clase, las subclases (con la excepción a la que nos referimos anteriormente), y todas las clases dentro del mismo paquete que accedan a los miembros. Este nivel de acceso se utiliza cuando es apropiado para una subclase de la clase tener acceso a los miembros, pero no las clases no relacionadas. Los miembros protegidos son como secretos familiares - no importa que toda la familia lo sepa, incluso algunos amigos allegados pero no se quiere que los extraños lo sepan.

Para declarar un miembro protegido, se utiliza la palabra clave protected. Primero echemos un vistazo a cómo afecta este especificador de acceso a las clases del mismo paquete.

Consideremos esta versión de la clase Alpha que ahora se declara para estar incluida en el paquete Griego y que tiene una variable y un método que son miembros protegidos.

 

package Griego;

class Alpha {

protected int estoyProtegido;

protected void metodoProtegido() {

System.out.println("metodoProtegido");

}

}

 

Ahora, supongamos que la clase Gamma, también está declarada como miembro del paquete Griego (y no es una subclase de Alpha). La Clase Gamma puede acceder legalmente al miembro estoyProtegido del objeto Alpha y puede llamar legalmente a su método metodoProtegido().

 

package Griego;

class Gamma {

void metodoAccesor() {

Alpha a = new Alpha();

a.estoyProtegido = 10; // legal

a.metodoProtegido(); // legal

}

}

 

Esto es muy sencillo. Ahora, investiguemos cómo afecta el especificador protected a una subclase de Alpha.

Introduzcamos una nueva clase, Delta, que desciende de la clase Alpha pero reside en un paquete diferente - Latin. La clase Delta puede acceder tanto a estoyProtegido como a metodoProtegido(), pero solo en objetos del tipo Delta o sus subclases. La clase Delta no puede acceder a estoyProtegido o metodoProtegido() en objetos del tipo Alpha. metodoAccesor() en el siguiente ejemplo intenta acceder a la variable miembro estoyProtegido de un objeto del tipo Alpha, que es ilegal, y en un objeto del tipo Delta que es legal.

Similarmente, metodoAccesor() intenta invocar a metodoProtegido() en un objeto del tipo Alpha, que también es ilegal.

 

import Griego.*;

package Latin;

class Delta extends Alpha {

void metodoAccesor(Alpha a, Delta d) {

a.estoyProtegido = 10; // ilegal

d.estoyProtegido = 10; // legal

a.metodoProtegido(); // ilegal

d.metodoProtegido(); // legal

}

}

 

Si una clase es una subclase o se cuentra en el mismo paquete de la clase con el miembro protegido, la clase tiene acceso al miembro protegido.

 

Public

El especificador de acceso más sencillo es 'public'. Todas las clases, en todos los paquetes tienen acceso a los miembros públicos de la clase. Los miembros públicos se declaran sólo si su acceso no produce resultados indeseados si un extraño los utiliza. Aquí no hay secretos familiares; no importa que lo sepa todo el mundo.

Para declarar un miembro público se utiliza la palabra clave public. Por ejemplo:

 

package Griego;

class Alpha {

public int soyPublico;

public void metodoPublico() {

System.out.println("metodoPublico");

}

}

 

Reescribamos nuestra clase Beta una vez más y la ponemos en un  paquete diferente que la clase Alpha y nos aseguramos que no están relacionadas (no es una subclase) de Alpha.

 

import Griego.*;

package Romano;

class Beta {

void metodoAccesor() {

Alpha a = new Alpha();

a.soyPublico = 10; // legal

a.metodoPublico(); // legal

}

}

 

Como se puede ver en el ejemplo anterior, Beta puede inspeccionar y modificar legalmente la variable soyPublico en la clase Alpha y puede llamar legalmente al método metodoPublico().

 

siguiente