Programación Orientada a Objetos
Como mencionamos previamente, las variables pueden tener distintos tipos de datos. Así mismo, Java establece una clara diferencia entre estos tipos de datos, y los agrupa en dos grandes categorías: tipos de datos primitivos y tipos de datos no primitivos (o clases). Si bien hemos analizado esta primer categoría, necesitaremos conocer algunos conceptos fundamentales antes de poder comprender los tipos de datos no primitivos.
Hasta el momento, hemos visto que la programación es una forma de resolver problemas, y para ello hemos utilizado un paradigma de programación llamado imperativo. En este paradigma, el programa se compone de una serie de instrucciones que se ejecutan secuencialmente, y que modifican el estado del programa.
No obstante, el enfoque imperativo tiene sus limitaciones. Razón por la cual han surgido nuevas maneras de interpretar y representar los sistemas que buscamos implementar. Una de ellas es el paradigma orientado a objetos.
El Paradigma Orientado a Objetos, o bien la Programación Orientada a Objetos (POO), es una forma de programar que tiene como objetivo expresar los elementos de la vida real como unidades esenciales del software que desarrollamos. Así como también, las relaciones que existen entre ellos.
Será vital que comprendamos cómo funciona este paradigma, ya que Java está basado fundamentalmente sobre él. Por este motivo, aprenderemos los conceptos básicos de la Programación Orientada a Objetos, y cómo aplicarlos.
Clases y Objetos
Para comenzar, debemos entender que POO se basa en los conceptos de clases y objetos. Estos elementos permitirán estructurar un programa en piezas simples y reutilizables.
Dentro del contexto POO, un objeto es un componente que tiene un rol específico y puede interactuar con otros. En este sentido, se trata de establecer una relación entre un objeto del mundo real y un componente de software.
Por otro lado, una clase puede entenderse como una plantilla que define las características y el comportamiento de un objeto. Es decir, una clase es un modelo que define las propiedades y el comportamiento de un objeto.
Es importante distinguir estos dos conceptos que, a menudo, se confunden. Para ello, introduciremos un nuevo concepto: la instanciación.
De esta forma, podemos tener diferentes objetos que pertenezcan a una misma clase. Podemos usar como sinónimos las palabras ‘objeto’ e ‘instancia’ de una clase.
En retrospectiva, recordando el paradigma imperativo, podemos asociar una clase con un tipo de dato. Es decir, una clase es un tipo de dato no primitivo. De igual manera, podemos asociar un objeto con una variable. Es decir, un objeto es una variable cuyo tipo de dato es una clase.
Por ejemplo, si buscamos representar un auto, podemos definir una clase Auto
que tenga las propiedades de este objeto en el mundo real. Posteriormente, veremos cómo definir una clase en Java. Por ahora, supongamos que ya tenemos una clase Auto
definida.
Entonces, empleando esta clase podemos crear un objeto carro
que represente un auto en particular. En primer lugar, declararemos el objeto carro
, tal y como lo haríamos con una variable de tipo primitivo:
1Auto carro;
En este punto podemos afirmar que el objeto carro
existe, pero aun no se encuentra instanciado y, en términos de POO, el objeto no “está vivo”.
Para instanciar un objeto en Java, debemos utilizar la palabra reservada new
seguida del nombre de la clase que queremos instanciar.
1Auto carro = new Auto();
De esta forma, el objeto carro
se encuentra instanciado y, en términos de POO, el objeto “está vivo”. Aunque pueda parecer un poco extraño referirse a un objeto de esta manera, es importante entender que sólo los objetos “vivos” pueden interactuar con otros objetos.
Sin realizar el proceso de instanciación, un objeto no es más que una variable que ocupa un espacio en memoria del cual no podemos aprovechar nada. Por lo tanto, la instanciación es un paso fundamental para poder utilizar un objeto.
Recordando cuando hablamos del tipo de dato String
, hemos mencionado que es un tipo de dato no primitivo. Es decir, String
es una clase que representa una cadena de caracteres.
Hasta el momento, solo hemos visto cómo instanciar cadenas de caracteres asignándolas a literales.
1String cadena = "Hola Mundo";
Sin embargo, como String
es una clase, si lo deseamos podemos instanciar un objeto de esta clase utilizando el operador new
. En particular, veremos que podemos indicar durante la instanciación la cadena de caracteres que queremos representar.
1String cadena = new String("Hola Mundo");
Estas sentencias son equivalentes y, en ambos casos, estamos instanciando un objeto de la clase String
. No obstante, la primera forma es la más utilizada, ya que es más sencilla y legible, razón por la cual la hemos utilizado hasta el momento.
Así también, si prestamos atención a los ejemplos que hemos visto hasta el momento, podemos notar que hemos instanciado objetos de varias clases. Por ejemplo, Scanner
para leer datos desde la consola, Array
para representar un arreglo, etc. Ante todos estos casos, hemos de entender que trabajar con objetos es imprescindible en Java.
Atributos y Métodos
Ahora que hemos contextualizado como funciona el paradigma orientado a objetos, debemos mencionar como es que logramos representar un objeto como un componente de software. La manera de lograr esto es muy simple, mediante las clases.
Para esto las clases cuentan con dos elementos fundamentales, que son los atributos y los métodos.
Los atributos representan las características que definen o identifican a un objeto. Por ejemplo, si queremos representar un auto, podemos definir los atributos marca
, modelo
, color
, año
, placa
, cilindraje
, transmisión
, tracción
, combustible
, kilometraje
, precio
, estado
, etc.
Por otro lado, los métodos representan el comportamiento de un objeto. Es decir, las acciones o funcionalidades que puede realizar un objeto. Por ejemplo, si queremos representar un auto, podemos definir los métodos encender
, apagar
, acelerar
, frenar
, cambiarMarcha
, cambiarTracción
, cambiarCombustible
, cambiarEstado
, etc.
Tomando como ejemplo la clase String
, con la que venimos trabajando, podemos apreciar que cuenta con atributos y métodos. Por ejemplo, el atributo length
nos permite conocer la longitud de una cadena de caracteres. Por otro lado, el método toUpperCase
nos permite convertir una cadena de caracteres a mayúsculas.
1String cadena = "Hola Mundo";2
3System.out.println(cadena.length()); // 104System.out.println(cadena.toUpperCase()); // HOLA MUNDO
length
es un atributo de la clase String
, el cual, para esta instancia en particular, tiene un valor de 10
.
Por otro lado, el método toUpperCase
es un comportamiento de la clase String
, el cual, para esta instancia en particular, devuelve una nueva cadena de caracteres con todos sus caracteres en mayúsculas ("HOLA MUNDO"
).
Referencias
Cuando describimos la manera en que funcionan las variables, mencionamos que estas almacenan valores en memoria. Sin embargo, dependiendo del tipo de dato de la variable, esta definición puede variar.
Por un lado, las variables de tipo primitivo como int
, float
, double
, boolean
, char
, etc. almacenan directamente los valores que se les asignan. Por ejemplo, si declaramos una variable de tipo int
y le asignamos el valor 10
, la variable almacenará directamente el valor 10
en el espacio de memoria que ocupa.
Sin embargo, esto no ocurre cuando las variables pertenecen a una clase, donde los valores que se almacenan en las variables son referencias a los objetos.
Es decir, las variables no almacenan directamente los valores de los objetos, sino que almacenan una referencia a la ubicación en memoria donde se encuentra el objeto.
Por ejemplo, si declaramos una variable de tipo String
y le asignamos el valor "Hola Mundo"
, la variable no almacenará directamente el valor "Hola Mundo"
en el espacio de memoria que ocupa. De igual manera, si declaramos una variable de tipo Array
, ArrayList
, HashMap
, Auto
, etc. y le asignamos un valor, la variable no almacenará directamente el valor que le asignamos.
Esto, puede ser difícil de comprender, pero es importante que lo entendamos, ya que podría llevarnos a cometar errores al momento de programar.
Para entender mejor esto, expandiremos los conceptos hablando sobre el “stack” y el “heap” en el siguiente apartado.