Clases en Java
En Java todo objeto (las clases) se manipula a través de una referencia
Esa referencia guarda el identificador del objeto
Y se encarga de enviar los mensajes al objeto asociado
El tipo de la referencia indica qué tipo de mensajes puede enviar
Modificadores de acceso
Al crear clases surge la necesidad de indicar el uso de sus atributos y métodos
Algunos atributos serán internos:
-
No deben modificarse
-
No es necesario saber de su existencia
Lo mismo ocurre con los métodos:
-
No deben invocarse desde el exterior de la clase
-
No hace falta conocer su utilidad
Los modificadores de acceso controlan la visibilidad de los miembros de la clase
Indican cuales de ellos son de interés para el exterior
Java ofrece cuatro modificadores de acceso:
- private
es la visibilidad más restrictiva
Sólo permite la invocación desde la propia clase
- public
es la visibilidad menos restrictiva
Permite la invocación desde cualquier clase
- package
es la visibilidad por defecto
Sólo permite la invocación desde las clases que pertenezcan a su mismo paquete
- protected
es la visibilidad asociada a la herencia
Sólo permite la invocación desde las clases derivadas
Constructores
Cuando una clase no declara ningún constructor se crea automáticamente un constructor por defecto
En el ejemplo el constructor ha sido llamado desde el método main y es el encargado de dejar al objeto en un estado inicial con el que podremos trabajar con él
Como no hemos creado el constructor, se ha usado automáticamente el constructor por defecto
En el ejemplo se ha definido una clase llamada Persona que tiene sólo el atributo nombre que es privado
Se ha usado el constructor para inicializarlo y para acceder a él (como es privado no es posible acceder directamente) se han añadido los métodos get y set
En caso de no haber usado el constructor para inicializarlo con new, al usar p habríamos obtenido un valor null, ya que el objeto todavía no estaría definido en memoria
Para referirnos a un atributo de la propia clase usaremos la palabra reservada this
En este caso se ha usado para referirnos al atributo nombre
Constructor con parámetros
Cuando necesitamos añadir información externa, podemos usar los constructores con parámetros
Un constructor puede declarar los parámetros que necesite para crear el objeto
Este ejemplo es muy similar al anterior, sin embargo ahora tenemos dos constructores, un constructor por defecto y un constructor con parámetros
En el caso de que tengamos el mismo nombre en un parámetro y un atributo, podremos distinguirlos si usamos la palabra reservada this
Nos ocurría con el parámetro nombre y el atributo nombre, al usar this han quedado desambigüados
Un constructor también puede estar sobrecargado, es decir, recibe distintos tipos de argumentos para tener un comportamiento distinto
En este ejemplo se han definido las clases Point y Rectangle
Definiendo varios constructores con parámetros de distintos tipos para la clase Rectangle
Así hemos sobrecargado el constructor consiguiendo distintos resultados en el método main
Atributos static
En una clase se describen los atributos de cada objeto (atributos de instancia)
A veces se necesitan atributos que sean compartidos por todos: que permanezcan a la clase
La palabra reservada static declara un atributo de clase
En el ejemplo se ha declarado un atributo static de tipo int, no es una clase pero como puede verse también se pueden usar tipos primitivos
Para acceder al atributo static le hemos indicando el nombre de la clase, en este caso A
No es necesario que haya ninguna instancia de clase para poder acceder al atributo
Se crean al cargar la clase y se inicializarán con los valores del constructor por defecto
En el ejemplo se han usado las clases Cronometro y A inicializando sus atributos en un bloque static
Con static no deben crearse objetos nuevos porque se reiniciaran con cada nueva instanciación
Los atributos de clase se inicializan mediante un bloque static
Los bloques static se ejecutan al cargar la clase
Se pueden considerar como un constructor más de la clase
En este ejemplo se ha reducido el código al usar una alternativa equivalente al inicializar en la propia declaración
Métodos static
De la misma forma que hay atributos static hay métodos static
Se recomienda su uso con métodos de utilidad que no se realizan sobre ningún objeto pero sí sobre tipos primitivos
Por ejemplo la clase Math ofrece los métodos static sqrt, random, pow, etc
Se declaran mediante la palabra static
En el ejemplo se ha declarado el método static f en la clase A
Para invocarlos se usa el nombre de la clase, en este caso la clase A
Un método static solo puede acceder:
-
a atributos static
-
a otros métodos static
En el ejemplo se ha añadido una referencia al método static g lo que nos permite acceder a los elementos de instancia de la clase A sin que fueran static
Hay que tener en cuenta que para acceder a elementos de instancia se debe obtener una referencia previamente
Herencia
Una clase derivada podrá añadir nuevas operaciones (ampliar el número de mensajes que entiende el objeto)
En el ejemplo se han creado las clases A y B
Como B heredaba los métodos de A y por tanto, podía usar metodoA sin problemas
Sin embargo, A no heredaba los métodos de B, por eso nos daba un error de ejecución
En el ejemplo se ha usado una clase derivada para redefinir operaciones
Redefinir un método consiste en dar una implementación distinta al mismo mensaje (que será tratado por otro método)
Es importante no confundir la sobrecarga de un método con su redefinición
En el ejemplo se ha redefinido el método metodoA de la clase A en la clase B, reutilizandolo sustituyendo su implementación
Se ha utilizado la palabra reservada super para referirse a los métodos de la clase base
También puede utilizarse la palabra reservada super para referirse a los atributos de la clase base
En el ejemplo hay un objeto base Vehiculo, dos derivados Coche y Bicicleta
Un aspecto fundamental de la herencia es la relación entre el tipo base y el tipo derivado
La clase derivada tiene, al menos, las operaciones de la clase base
Es decir, un objeto de la primera clase entiende todos los mensajes que pudiera entender un objeto de la segunda
Por tanto, cualquier referencia del tipo base podrá apuntar a un objeto del tipo derivado (ya que entiende todos sus mensajes)
Polimorfismo
La herencia permite que distintas clases puedan compartir un conjunto común de operaciones (las de la clase base)
El polimorfismo (dynamic binding) permite que aunque dos objetos acepten los mismos mensajes puedan realizarlos de forma diferente
En el ejemplo podemos ver que gracias al polimorfismo, a pesar de que v es de tipo Vehiculo se tiene en cuenta el tipo de objeto al que apuntaba
Es importante diferenciar los dos tipos que participan en el envío de un mensaje:
- El tipo de referencia
es el que determina los mensajes que ésta puede transmitir a su objeto asociado
Por tanto, sólo se podrá asociar a objetos que entiendan, al menos, dichos mensajes (objetos del mismo tipo o de un tipo derivado)
- El tipo del objeto asociado
es el que determina que método es el que despachará el mensaje recibido
Los constructores y la herencia
Los constructores no pueden heredarse
En el ejemplo se han declarado dos constructores para la clase A y luego se ha heredado en la clase B
Al tratar de usar el constructor con parámetro entero nos dará un error porque B no poseía un constructor apropiado a pesar de que A sí que tenía
Una clase solo podrá usar los constructores que declare
Si no se ha declarado ninguno se le asignará el constructor por defecto
En el ejemplo se han declarado dos constructores para la clase A y luego se ha heredado en la clase B
Pero en esta ocasión se ha hecho una llamada al constructor por defecto de la clase base dentro del constructor de la clase derivada
Es obligatorio que se invoque algún constructor de la clase base (para garantizar su estado)
Mediante super se puede seleccionar el constructor adecuado
super solo puede aparecer una vez y debe ser la primera línea del constructor
Admitirá sólo los parámetros que admitan los constructores de la clase base
Clases abstractas
Una clase abstracta es aquella que tiene uno o más métodos abstractos
Un método abstracto es aquel que no ha sido implementado en la clase base
Su código deberá ser aportado en cada una de las clases derivadas
Para definir una clase como abstracta se utiliza la palabra reservada abstract
En el ejemplo se ha definido la clase abstracta Vehiculo con dos métodos abstractos
Como puede verse, los métodos también se definen usando la palabra reservada abstract
Las clases abstractas tienen la restricción de que no pueden instanciarse (por tener métodos sin implementar)
Si una clase derivada no redefine todos los métodos abstractos, será a su vez una clase abstracta
Creación de clases abstractas:
-
Identificar las operaciones comunes que deben tener los objetos a manipular
-
Crear con ellos una clase abstracta base
-
Identificar las operaciones a redefinir y dar una implementación al resto
Uso de clases abstractas:
-
Su único objetivo es la derivación
-
Las clase derivadas tendrán que implementar los métodos abstractos
-
Las demás podrán heredarse, sustituirse o ampliarse
Clases final
Puede que se desee que una clase no pueda ser derivada (por razones de seguridad, no se desea que nadie pueda sustituirla por una clase derivada)
Se utiliza la palabra reservada final para que la clase no pueda ser derivada
En el ejemplo hemos intentado heredar con la clase B la clase A que era final
Como hemos definido A como final el interprete no permitirá que tenga clases heredadas
En el el ejemplo se ha definido un método final y otro que no lo es
También hay un nivel intermedio: que se pueda derivar de la clase pero no ciertos métodos
Un método también puede usar la palabra reservada final
Un método final no puede ser redefinido
En el ejemplo se ha intentado modificar un atributo final, el intérprete nos dará un error
Hacer una clase o método final es una gran restricción
Solo debe hacerse en situaciones especiales y poniendo sumo cuidado
Un atributo final no puede ser modificado una vez que haya sido iniciado
En el ejemplo se han definido dos variables llamadas x como final
Si el valor del atributo final es el mismo para todas las instancias se está desaprovechando espacio en memoria
En este ejemplo se han utilizado variables constantes para solucionar el problema del ejemplo anterior
En Java las constantes se declaran combinando las palabras reservadas static seguida de final
Las constantes no ocupan memoria y por convenio, se suelen definir con un nombre en mayúsculas
Downcasting
Cuando una clase no deriva explícitamente de otra clase entonces deriva implícitamente de la clase java.lang.Object
La clase Object no es un tipo primitivo, pero es la clase de la cuál derivan todas las clases del lenguaje y aquellas que el programador cree
Sin embargo, no es necesario usar la palabra reservada extends porque deriva implícitamente
Por tanto, toda clase de Java es un Object y heredará los métodos de este
Algunos métodos destacados de la clase Object:
- boolean equals(Object)
- void finalize()
- int hashCode()
- String toString()
Hay ocasiones en las que se sabe con certeza que el objeto es de la clase adecuada
En estos casos se podrá forzar la asignación mediante un cast
En el ejemplo se han utilizado las clases Vehiculo y Canoa que hereda de ella
Para luego hacer una declaración de un Vehiculo v para el que se ha utilizado el constructor de Canoa
Y se ha declarado una Canoa c para la que se conocía previamente el tipo (Canoa) por lo que se ha realizado un cast
El cast comprueba que el objeto es de la clase adecuada y realiza la asignación
En caso de no ser de la clase adecuada o no es capaz de realizar la asignación lanzará una excepción ClassCastException
Operador instanceof
El operador instanceof permite averiguar si un objeto pertenece a una determinada clase
En el ejemplo se ha realizado dos comprobaciones con instanceof
Cuando hemos probado con Object devolverá siempre True porque es un supertipo de la clase de la instancia
Cuando hemos probado con Canoa hemos comprobado si el cast puede aplicarse
De esta forma podemos evitar tener que controlar la excepción ClassCastException
Interfaces
Los interfaces, al igual que las clases, son una forma de crear objetos
Se caracterizan porque sólo se declaran un comportamiento (y/o constantes)
-
No tienen atributos
-
No tienen código asociado para los métodos
Se definen con la palabra reservada interface de forma similar a como se hace con las clases
En el ejemplo se ha definido la interfaz unInterface con los métodos metodo1 y metodo2
De forma similar a como ocurría con las clases abstractas, como no tienen implementación es posible instanciar la interfaz
Sólo es posible usarla para crear nuevos tipos de datos (otras clases o interfaces)
En este ejemplo se ha implementado la interfaz unInterface heredandola en la clase UnaClase
Cuando la clase UnaClase ha heredado los métodos de unInterface se ha convertido en un subtipo de esa interfaz
Por esa razón hay que darles una implementación en la clase, ya que sinó estaríamos obligados a declararla abstract
Pero precisamente por haber utilizado una interfaz, el interprete nos mostrará un mensaje de error diciéndonos qué métodos concretos no han sido implementados
Diferencias entre clase e interface
Clase:
-
Tipo que al extenderlo mediante herencia se obtienen su comportamiento y su implementación (tanto atributos como sus métodos)
-
Ventaja: Menor codificación al crear nuevos subtipos ya que su comportamiento viene con su implementación
-
Inconveniente: Sólo se puede derivar de una de ellas, no está permitida la herencia múltiple
Interfaces:
-
Tipo que al extenderlo mediante herencia se obtiene solamente su comportamiento (descripción de los métodos)
-
Ventaja: Se pueden heredar varios interfaces sin conflictos
-
Inconveniente: Hay que codificar la implementación de cada método para cada subtipo heredado