Clases definidas por el usuario
Como mencionamos en el tema anterior, trabajar con objetos es imprescindible para poder programar en Java. Si bien es cierto que Java nos provee de una serie de clases que podemos utilizar, también es cierto que podemos crear nuestras propias clases.
En esencia, estaremos definiendo un nuevo tipo de dato, que podremos utilizar en nuestro programa. Además, como sabemos, los objetos son instancias de una clase, por lo cual típicamente suele referirse a las clases como plantillas, prototipos o moldes de los objetos.
El hecho de definir una clase implica que necesitamos representar un concepto, un objeto, una entidad, etc. que sea relevante para nuestro programa. Por ejemplo, si estamos desarrollando un sistema de gestión de una biblioteca, es probable que necesitemos representar a los libros, a los usuarios, a los préstamos, etc. En este caso, cada uno de estos conceptos podría ser representado por una clase.
Para definir una clase en Java, utilizamos la palabra reservada class
, seguida del nombre de la clase y de un bloque de código entre llaves.
1class <ClassName> {2 // Cuerpo de la clase3}
Por ejemplo, si quisiéramos definir una clase Car
, podríamos hacerlo de la siguiente manera:
1class Car {2 // Cuerpo de la clase3}
Claramente, esto no es suficiente para representar a un auto, pero es la mínima expresión que necesitamos para definir una clase. En este caso, la clase Car
no tiene ningún atributo ni ningún método, por lo cual no es muy útil. Sin embargo, es una clase válida y podría ser utilizada en un programa.
1Car car = new Car();
En este caso, estamos creando una instancia de la clase Car
y asignándola a la variable car
. Es decir, creamos un usuario en particular, que podemos utilizar en nuestro programa.
Así diferenciamos semánticamente entre las clases y los objetos, pero siempre podemos darles nombres más descriptivos a nuestras instancias, por ejemplo:
1Car deportivoRojo = new Car();
Atributos
Para que una clase sea útil, es necesario que tenga atributos y métodos que nos permitan representar a la entidad que estamos modelando. En el ejemplo planteado, un auto podría tener un color, una marca, un modelo, el kilometraje, entre otros atributos.
Para definir un atributo, debemos indicar el tipo de dato que va a almacenar, seguido del nombre del atributo. Es decir, un atributo es una variable que pertenece a la clase. Por ejemplo, si quisiéramos agregar un atributo color
a la clase Car
, podríamos hacerlo de la siguiente manera:
1class Car {2 String color;3}
De igual manera, podemos definir otros atributos que necesitemos.
1class Car {2 String color; // Color3 String brand; // Marca4 String model; // Modelo5 int mileage; // Kilometraje6 boolean isOn; // Está encendido7}
Ahora, podemos crear diferentes instancias de la clase Car
y asignarles valores a sus atributos.
1Car deportivoRojo = new Car();2deportivoRojo.color = "rojo";3deportivoRojo.brand = "Ferrari";4deportivoRojo.model = "F40";5deportivoRojo.mileage = 10000;6deportivoRojo.isOn = false;7
8Car familiarAzul = new Car();9familiarAzul.color = "azul";10familiarAzul.brand = "Ford";11familiarAzul.model = "Focus";12familiarAzul.mileage = 200000;13familiarAzul.isOn = true;
Al igual, que con cualquier variable, podemos asignarle valores a los atributos de un objeto y utilizarlos en nuestro programa.
Por otro lado, es importante tener en cuenta que los atributos de una clase son variables de instancia. Es decir, cada instancia de la clase tiene sus propios atributos, que pueden tener valores diferentes a los de otras instancias de la misma clase, como vimos en el ejemplo anterior.
Así también, podemos asignar valores a los atributos al momento de definirlos, utilizando el operador de asignación =
. Estos valores serán los valores por defecto de los atributos de cada instancia de la clase.
1class Car {2 String color; // Color3 String brand; // Marca4 String model; // Modelo5 int mileage = 0; // Kilometraje6 boolean isOn = false; // Está encendido7}
De esta manera, cada vez que creemos una instancia de la clase Car
, los atributos mileage
e isOn
tendrán los valores por defecto que definimos en la clase, lo cual puede ser útil en algunos casos.
1Car deportivoVerde = new Car();2System.out.println(deportivoVerde.mileage); // 03System.out.println(deportivoVerde.isOn); // false
En este punto, hemos dotado a nuestra clase con características (o atributos) que nos permiten identificar a un auto. Si bien podrían tener más atributos, nos quedaremos con estos tres por el momento.
Sin embargo, que una clase permita almacenar datos no es suficiente. También necesitamos que tenga comportamiento. Es decir, que nos permita realizar acciones sobre los datos que almacena. Para ello, utilizaremos los métodos.
Métodos
Los métodos son bloques de código que nos permiten realizar acciones sobre los datos que almacena una clase. En esencia, es comportamiento asociado al objeto que estamos modelando.
Para definir un método debemos tener en cuenta las siguientes consideraciones:
- Un método debe tener un nombre, que nos permita identificarlo. Generalmente, se utiliza un verbo en infinitivo para nombrar a los métodos. Por ejemplo, si quisiéramos definir un método que nos permita encender el auto, podríamos llamarlo
turnOn
oencender
. - Un método puede recibir parámetros o no, los cuales nos permiten enviarle información al método para que pueda realizar el comportamiento que necesitamos.
- Un método puede devolver un valor o no, dependiendo de si necesitamos que nos devuelva información o no.
En Java, para definir un método, debemos indicar el tipo de dato que va a devolver (o void
si no devuelve nada), seguido del nombre del método y entre paréntesis, los parámetros que recibe.
1<tipo que retorna> <nombre método>([<tipo de parámetro> <nombre parámetro>, ...]) {2 <cuerpo del método>3 [return <valor de retorno>];4}
Nótese que los parámetros son opcionales, por lo cual los paréntesis pueden estar vacíos. Sin embargo, los paréntesis son obligatorios, ya que indican que estamos definiendo un método.
Por otro lado, un método puede tener una sentencia return
o no. En caso de tenerla, esto indica que el método devuelve un valor y el tipo de dato del valor de retorno debe coincidir con el tipo de dato de retorno del método. En caso de no tenerla, el tipo de dato de retorno del método debe ser void
.
Es importante resaltar que, al igual que con las variables, debemos declarar un método dentro de una clase.
Valor de retorno
Los métodos pueden devolver un valor o no, este valor es el resultado de la ejecución del método y denominado valor de retorno. Este valor puede ser de cualquier tipo de dato, incluso de un tipo de dato definido por el usuario (es decir, una clase).
Por ejemplo, si quisiéramos conocer la marca de un auto podríamos definir un método que nos devuelva dicho valor. En este caso, el tipo de dato de retorno del método sería String
, ya que la marca es un String
. Además, en este caso, el método no recibe ningún parámetro, ya que no necesitamos enviarle información adicional para que nos devuelva la marca del auto.
1String getBrand() {2 // Cuerpo del método3 return // Valor de retorno4}
Por otro lado, veremos que en ocasiones no necesitamos, o no deberíamos, devolver ningún valor.
Por ejemplo, si quisiéramos representar el comportamiento para encender el auto, podríamos hacerlo definiendo el método turnOn
para la clase Car
, podríamos hacerlo de la siguiente manera:
1void turnOn() {2 // Cuerpo del método3}
En este caso, el método turnOn
no recibe ningún parámetro y no devuelve ningún valor.
En ocasiones veremos que un método puede realizar una acción, pero no devuelve ningún valor porque no es necesario. Por ejemplo, porque se modifica el estado de un objeto, o porque se muestra un mensaje por pantalla, etc. En estos casos, el tipo de dato de retorno del método es void
, lo cual indica la carencia de un valor de retorno.
Referencia this
Entonces, si quisiéramos definir el método turnOn
para la clase Car
, deberíamos modificar el atributo isOn
a false
para indicar que el auto está encendido. Y para el método getBrand
, deberíamos devolver el valor del atributo brand
del auto.
Podríamos pensar que las siguientes definiciones para estos métodos son correctas:
1class Car {2 String color; // Color3 String brand; // Marca4 String model; // Modelo5 int mileage; // Kilometraje6 boolean isOn; // Está encedido7
8 void turnOn() {9 isOn = true;10 }11
12 String getBrand() {13 return brand;14 }15}
No obstante, ¿cómo sabemos que estamos cambiando el valor del atributo isOn
del auto correcto? Si tenemos dos instancias de la clase Car
, deportivoRojo
y familiarAzul
¿a cuál de ellas le estamos cambiando el valor del atributo isOn
?
Para indicar que queremos modificar un atributo de una instancia en particular, utilizamos la palabra reservada this
. Esta nos permite referenciar al objeto que está ejecutando el método.
1class Car {2 String color; // Color3 String brand; // Marca4 String model; // Modelo5 int mileage; // Kilometraje6 boolean isOn; // Está encedido7
8 void turnOn() {9 this.isOn = true;10 }11
12 String getBrand() {13 return this.brand;14 }15}
Entonces, dependiendo de la instancia que invoque al método turnOn
, se modificará el atributo isOn
de esa instancia en particular.
1Car deportivoRojo = new Car();5 collapsed lines
2deportivoRojo.color = "rojo";3deportivoRojo.brand = "Ferrari";4deportivoRojo.model = "F40";5deportivoRojo.mileage = 10000;6deportivoRojo.isOn = false;7
8Car familiarAzul = new Car();5 collapsed lines
9familiarAzul.color = "azul";10familiarAzul.brand = "Ford";11familiarAzul.model = "Focus";12familiarAzul.mileage = 200000;13familiarAzul.isOn = true;14
15System.out.println(deportivoRojo.isOn); // false16System.out.println(familiarAzul.isOn); // true17
18deportivoRojo.turnOn();19
20System.out.println(deportivoRojo.isOn); // true21System.out.println(familiarAzul.isOn); // true
De igual manera, ocurrirá con el método getBrand
, que devolverá el valor del atributo brand
de la instancia que invoque al método.
1System.out.println(deportivoRojo.getBrand()); // Ferrari2System.out.println(familiarAzul.getBrand()); // Ford
Es importante tener en cuenta que la referencia this
solo puede ser utilizada dentro de una clase. Fuera de ella carece de sentido, ya que no hay un objeto que la invoque.
Parámetros
Por otro lado, al definir un método veremos la necesidad de enviar información adicional a la que un objeto almacena. Por ejemplo, si quisiéramos cambiar el color de un auto, necesitaríamos enviarle al método el nuevo color que queremos asignarle.
Para ello, podemos definir un parámetro en el método, que nos permita recibir información adicional. Para definir un parámetro, debemos indicar el tipo de dato que va a recibir dicho parámetro, seguido del nombre del parámetro.
En este caso, el parámetro sería el nuevo color que queremos asignarle al auto, la cual debería ser del mismo tipo de dato que el atributo que queremos modificar (en este caso, color
es de tipo String
).
1class Car {2 String color; // Color3 String brand; // Marca4 String model; // Modelo5 int mileage; // Kilometraje6 boolean isOn; // Está encedido7
8 void turnOn() {9 this.isOn = true;10 }11
12 void paint(String newColor) {13 this.color = newColor;14 }15}
Por supuesto, podemos definir tantos parámetros como necesitemos para nuestros métodos.
Punto de entrada de un programa
Al ser Java un lenguaje orientado a objetos, es importante tener en cuenta que todo el código que escribamos debe estar contenido dentro de una clase. Lo que es más, hemos estado definiendo clases desde el primer momento, ya que cada vez que escribimos un programa, estamos definiendo una clase.
Si recordamos, para ejecutar un programa en Java, definimos la clase Main
. La misma contiene el método main
, que es el punto de entrada de nuestro programa. Es decir, para ejecutar un programa en Java, debemos definir la clase que coordina la ejecución del mismo a través de un comportamiento, que es el método main
.
1class Main {2 public static void main(String[] args) {3 // Cuerpo del método4 }5}
Este método, a diferencia de otros, siempre debe tener este nombre y esta firma. Es decir, siempre debe llamarse main
y recibir un parámetro de tipo String[]
(un array de String
). Además, siempre debe ser public
y static
(más adelante hablaremos de estos modificadores).