4 Referencias¶
Introducción¶
Una referencia en cualquier lenguaje de programación hace alusión a la posición en memoria RAM que tiene una variable.
Una variable es conceptualmente un recipiente donde guardamos un dato. Java es un lenguaje fuertemente tipado. Las variables se definen siempre con un tipo asociado. No se puede meter en una variable ningún dato que no sea del tipo con que se definió.
Una variable es un lugar en la memoria donde se guarda un dato. Para ser exacto, este lugar en la memoria es la Pila o Stack. En el caso de los datos primitivos, como en int i = 5;
hay cuatro bytes en la Pila donde se almacena el número 5. Pero cuando se crea un objeto en Java, el objeto se guarda en una parte de la memoria llamada Heap. Cuando asignamos el objeto a una variable como en Vehicle car=new Vehicle();
, lo que guardamos en car es la dirección de memoria Heap donde está el objeto.
Stack en java es una sección de memoria que contiene métodos, variables locales y variables de referencia. La memoria de pila siempre se referencia en el orden de último en entrar primero en salir. Las variables locales se crean en la pila.
Heap es una sección de memoria que contiene objetos y también puede contener variables de referencia. Los atributos se crean en el heap.
Representación en memoria de tipos primitivos¶
Veamos paso a paso un ejemplo:
-
Cuando se declara una variable de un tipo primitivo, el compilador reserva un área de memoria para ella:
Figura 1 - Referencia de primitivos -
Cuando se asigna un valor, éste es escrito en el área reservada:
Figura 2 - Referencia de primitivos -
La asignación entre variables significa copiar el contenido de una variable en la otra:
Figura 3 - Referencia de primitivos -
La comparación entre variables compara los contenidos de las mismas:
public class Primitives {
public void show(){
int i = 10, j = 20;
System.out.printf("Valor de la variable i: %d\n", i);
System.out.printf("Valor de la variable j: %d\n", j);
i = j;
System.out.printf("Valor de la variable i: %d\n", i);
System.out.printf("Valor de la variable j: %d\n", j);
System.out.printf("i y j%s tiene el mismo contenido", i == j ? "" : " no");
}
public static void main(String[] args) {
new Primitives().show();
}
}
Representación en memoria de objetos¶
Supongamos que tenemos una clase Complex con dos atributos: r, i.
-
Cuando se declara una variable de un objeto, el compilador reserva un área de memoria para ella. Mientras no se cree ningún objeto con el operador new, lo que contendrá será null. La palabra reservada null indica que una variable que referencia a un objeto se encuentra de momento sin referenciar a ninguno:
Figura 4 - Referencia de objetos -
Cuando se crea el objeto llamando la constructor con el operador new, lo que se guarda en la variable c1 es la dirección de memoria donde se ha almacenado el objeto en el heap, es decir, la diferencia entre los tipos primitivos y los objetos es que en los primitivos la variable almacena el valor y en los objetos, la variable almacena la dirección de memoria donde se encuentra el objeto.
Figura 5 - Referencia de objetos -
La asignación entre variables de objetos significa copiar la dirección de memoria donde se encuentra el objeto:
En este caso, el valor de la variable c2 es 0x2A18, por lo tanto, ambas variables apuntan al mismo objeto. Si una de ellas modifica algún valor del objeto, también le afectará a la otra variable. Por ejemplo, si a c2 le cambiamos el valor de r a 9.7, si consultamos el valor de r de c1, también valdrá 9.7, en lugar de 7.2.
-
La comparación entre referencias no compara los contenidos de los objetos sino las direcciones de memoria, es decir, si apuntan al mismo sitio.
c2 == c1; // true porque apuntan al mismo sitio Complex c3 = new Complex(7.2, 2.4); c3 == c1; // false porque aunque tengan los mismos valores, no apuntan al mismo sitio
Para comparar el contenido de los objetos, se utiliza el método equals:
public class Objects {
public void show(){
Complex c1 = new Complex(7.2, 2.3);
Complex c2 = c1;
System.out.printf("C1 -> r: %.2f j: %.2f\n", c1.r, c1.j);
System.out.printf("C2 -> r: %.2f j: %.2f\n", c2.r, c2.j);
c1.r = 10;
c2.j = 7.6;
System.out.printf("C1 -> r: %.2f j: %.2f\n", c1.r, c1.j);
System.out.printf("C2 -> r: %.2f j: %.2f\n", c2.r, c2.j);
Complex c3 = new Complex(10, 7.6);
System.out.printf("C3 -> r: %.2f j: %.2f\n", c3.r, c3.j);
System.out.printf("c1 == c2 -> %b\n", c1 == c2);
System.out.printf("c1 == c3 -> %b\n", c1 == c3);
System.out.printf("c1 equals c3 -> %b\n", c1.equals(c3));
}
public static void main(String[] args) {
new Objects().show();
}
}
Garbage Collector¶
Garbage Collector (recolector de basura) es un programa que se ejecuta en el Java Virtual Machine que elimina los objetos que ya no están siendo utilizados por una aplicación Java. Es una forma de gestión automática de la memoria.
Un objeto es elegible para el recolector de basura cuando deja de existir alguna referencia hacia él. Veamos algunos ejemplos.
Existe un objeto referenciado por la variable c1. Cuando la variable c1 pierde la referencia porque se le asigna el null, se pierde cualquier forma de acceder al objeto, de modo que pasa a ser elegible para el recolector de basura.
En este segundo ejemplo, el objeto no pasa a ser elegible para ser recolectado pues aunque la variable c1 haya perdido la referencia porque se le asigna el null, todavía existe una referencia hacia el objeto por la variable c2
En este otro ejemplo, la variable c1 es reasignada, es decir, se le asigna otro objeto mediante el operador new, por lo tanto se pierde cualquier referencia al objeto creado al principio new Complex(7.2 , 2.4)
por lo que dicho objeto pasa a ser elegible para el recolector de basura.