Clases en Java

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