Introducción a Java — Parte I
Variables y memoria
Java es un lenguaje de programación portable a través de diversos Sistemas Operativos y Dispositivos, dado que su código se compila para una máquina virtual llamada JVM (Java Virtual Machine). Java es un lenguaje de programación Orientado a Objetos, lo cual nos permite programar sistemas complejos a tráves de clases y objetos. Además Java tiene un gran número de desarrolladores y librerías, por lo que puede ser utilizado para diversos fines, como crear aplicaciones de escritorio, servicios web, programas de administración y mucho más.
En esta serie de entregas analizaremos a fondo las partes más importantes de Java para tener un panorama general del lenguaje y poder ampliar los conocimientos de forma autodidacta.
Puntos de entrada o Clases Ejecutables
En Java la metodología de trabajo es a través de Clases o Interfaces, por lo que para definir un archivo, deberemos llamarlo igual que el nombre de su Clase o Interfaz definida, por ejemplo, si queremos crear un programa ejecutable que muestre el mensaje hola mundo
en la terminal, entonces, deberemos crear una clase llamada HolaMundo
o con algún nombre que se desee y está deberá estar contenida dentro del archivo llamado exactamente igual que la clase más la extensión .java
, es decir, el archivo se deberá llamar HolaMundo.java
.
Para poder ejecutar el archivo, este deberá contener una clase y dentro de la clase deberemos definir un Punto de Entrada, el cual se refiere a un método especial llamado public static void main(String[] args)
, exactamente así. Dicho método especial es estático, esto significa que el método no tiene acceso a las propiedades definidas en la clase, es decir, este método es independiente de la clase y sólo podrá acceder a otras propiedades o métodos estáticos. El método main
también es especial porque es ejecutado automáticamente por Java cuando se manda a llamar en modo ejecución, en resumen tenemos:
Una Clase de Java puede ser ejecutada desde una terminal, si y sólo si se ha definido un método especial
public static void main(String[] args)
. Dicho métodomain
será ejecutado y tódo su código contenido será evaluado.
Veamos entonces, un ejemplo de la clase llamada HolaMundo
, contenida en el archivo de texto plano HolaMundo.java
, que despliega el mensaje Hola Java
en la terminal y finaliza.
Como podemos observar en el código anterior, hemos definido una clase llamada HolaMundo
, la cual posee un Punto de Entrada (un método especial public static void main(String[] args)
). Dentro del método main
podemos observar la sentencia System.out.println("Hola Java")
, la cual imprime en la salida estándar System.out
el texto Hola Java
, dicha salida estándar es generalmente la terminal.
Variables y Memoria
Las variables nos permiten acceder a los datos almacenados en la memoria y existen dos tipo de variables, las primitivas, que son almacenadas en la Memoria Stack y las variables complejas, que son almacenadas en la Memoria Heap.
Las variables primitivas pueden almacenar datos simples, por ejemplo, números enteros o decimales, caracteres y bytes. Los tipos de datos asociados a las variables primitivas son int, float, double, char, byte
, entre otros. Las variables primitivas retienen valores absolutos mediante la Memoria Stack, la cuál se encarga de reservar memoria para cada tipo de dato según los bytes que requiera, por ejemplo, para un entero descrito en 32 bits, debería reservar 4 bytes (1 byte equivale a 8 bits). En la memoria stack, cada valor accedido genera una copia, por lo que asignar una variable primitiva a otra equivale a crear una nueva variable con una copia del valor de la otra variable, a esto se le llama copia por valor. Puedes consultar la documentación para más detalles: https://docs.oracle.com/javase/tutorial/java/nutsandbolts/datatypes.html
Las variables complejas son creadas a partir de instanciar una clase en un objeto. Una clase es una construcción de atributos y métodos como veremos adelante. Cuando una clase es instanciada, se refiere a que el programa solicita un nuevo espacio en memoria para almacenar todos los atributos de la clase, dicho espacio será único para cada instancia, por ejemplo, si la clase require almacenar dos números enteros, entonces cada instancia solicitará la memoria necesaria para almacenar dos números enteros (8 bytes). Entonces cada instancia reserva su propia memoria, pero dicha memoria reservada es compleja, dado que en cualquier momento podrían generarse nuevas instancias y el programa debe asignar rápidamente memoria de tamaño variable, por lo que se crea el concepto de Memoria Heap, la cual consiste en una región extensa de memoria, la cual está asignado la memoria para cada instancia y de tamaño variable, de este modo, cada que una instancia solicita memoria, se busca una región disponible en el heap y se le asigna. La desventaja es que hay un tiempo de asignación que al cabo de un rato afecta el rendimiento del programa, por lo que Java implementa un recolector de basura encargado de liberar la memoria para las instancias que ya no tienen referencia (una referencia a la instancia hace que la memoria siga viva). Cuando copiamos una variable compleja (desde ahora instancia) a otra variable compleja, no se crea una copia de la memoria, sino que simplemente ambas variables se refieren a la misma memoria y lo que le pase a una variable le pasa a la otra, esto se conoce como copia por referencia. Para ver más de este tema puedes ver el siguiente video: https://youtu.be/_y7k_0edvuY
Una convensión de los programadores Java es nombrar con mayúsculas las clases, y cada clase genera un tipo de dato personalizado, por lo que si te encuentras una variable cuyo tipo de dato comience por mayúscula, automáticamente debes saber que se trata de un tipo de dato complejo y las variables serán complejas, por ejemplo, si creamos la variable String mensaje;
o Persona persona
.
Vamos a ver un ejemplo de un programa que utiliza variables primitivas para calcular el siguiente número entero.
Ahora vamos a comparlo con un programa que utiliza variables complejas y sus implicaciones.
Podemos ver que cuando utilizamos variables primitivas, estas crearán copias de sus valores cuando sean enviadas a otros métodos o asignadas a otras variables. Sin embargo, cuándo utilicemos variables complejas (variables de clase), estarémos copiando la referencia a la memoria que le fue asignada a la instancia referente, por lo que si pasamos la variable a tráves de un método o la asignamos a otra variable, en realidad estaremos enviando la dirección de memoria del heap dónde se encuantran los datos del objeto, por lo que todas las variables referenciadas podrían ser modificadas. Para más detalles puedes visitar: https://www.geeksforgeeks.org/variables-in-java/
Las variables primitivas son puertas de acceso a los datos almacenados en la memoria stack, por convención iniciarán con minúscula como
int
,float
,double
, etc. Cuándo copiamos una variable primitiva en otra, se creará una copia de su valor en memoria. Las variables complejas igualmente son accesos a instancias almacenadas en la memoria heap, las cuales fueron previamente asignadas por el operadornew
y por convención comienzan con mayúsculas, cada clase genera un tipo de dato complejo, por ejemplo,String
,Integer
,Float
,Double
,Persona
, etc. Cuándo copiamos una variable compleja en otra, se copiará la referncia a la memoria dónde se almacena la instancia, por lo que ambos objetos estarán trabajando sobre la misma instancia y se afectarán entre sí.
Con esto concluimos está primer entrega que nos permite ver que la programación en Java parte de clases, dichas clases al poseer un método especial se pueden ejecutar. También hemos podido entender el concepto de variables y ahora somos capaces de almacenar datos en nuestro programa, tanto datos primitivos como complejos.
Ahora deberías poder resolver los siguientes ejercicios sin problemas.
Problema 1. Crear una clase llamada
Suma
la cuál contenga un punto de entradamain
, crear dos variables de tipoint
llamadasa
yb
inicializada con dos números que se desee, imprimir la suma dea
yb
.Problema 2. Crear un programa (una clase con punto de entrada) que defina una variable de tipo
String
inicializada enHola mundo de Java
e imprimir la longitud de la variable utilizando el métodolength()
.Problema 3. Investiga cómo funciona la clase
Scanner
del paquetejava.util
para leer un número entero desde el teclado. Hint: utilizaScanner sc = new Scanner(System.in)
ysc.nextInt()
. Agrega las excepciones necesarias.Problema 4. Utiliza la clase
Scanner
para leer una cadena de texto.Problema 5. Crea un programa que lea dos números enteros desde el teclado e imprima la suma de ellos. Nota: imprime antes un mensaje para que el usuario sepa que se están esperando los números.