Archivo de la categoría: Programación

La programación es el proceso de diseñar, codificar, depurar y mantener el código fuente de programas

Vistas

Vistas

Las vistas se utilizan en situaciones en las que por motivos de seguridad o de rendimiento, queremos ocultar ciertos datos a los usuarios

En el ejemplo, un cliente necesita saber el número operación de un préstamo a su nombre, el ‘C-9732‘ y el nombre de la sucursal que lo ha tramitado, pero para esa operación no es necesario que vea el saldo o más datos relacionados

Con la consulta anterior resolvíamos lo que el cliente necesitaba, pero le dábamos más datos de los necesarios, pudiendo poner a la entidad bancaria en una situación que podría ser insegura

Las relaciones que no forman parte del modelo lógico pero son visibles a los usuarios mediante relaciones virtuales, se denominan vistas

Definición básica de vistas

CREATE VIEW

Las relaciones se definen mediante el comando CREATE VIEW:

\text{CREATE VIEW v} AS r

Donde r es una expresión de consulta y v es el nombre de la vista

En el ejemplo se ha creado una vista que trata de resolver el problema del ejemplo anterior

Sin embargo, a pesar de que hemos mejorado la seguridad al no mostrar al usuario los saldos, no resuelve el problema, aún deberíamos hacer la subconsulta siguiente para resolverlo:

En el ejemplo se ha tratado la vista como si fuera una relación más

Sin embargo, para el sistema gestor de bases de datos es una relación virtual por ser una vista

Al ser una relación virtual podemos hacer operaciones de consulta SELECT

Pero no podemos hacer operaciones de actualización como INSERT, UPDATE ó DELETE

En el ejemplo se ha creado una vista llamada prestamo(nombre_sucursal, total) que permite obtener los nombres de todas las sucursales y el saldo total que tienen

Como puede verse, es posible devolver cualquier atributo y trabajar normalmente con las consultas

En este caso se ha devuelto el atributo nombre_sucursal y se ha utilizado la función de agregación COUNT despues de usar la cláusula GROUP BY con los atributos nombre_sucursal y saldo

En el ejemplo se ha definido una vista a partir de otra para resolver la consulta que obtiene todos números de cuentas de la sucursal de ‘Navacerrada

Para ello hemos usado la vista llamada todas_las_cuentas

Ya que es tomado como una relación virtual para el sistema gestor de bases de datos por ser una vista

Hay que tener en cuenta que anidar vistas sobre si mismas puede producir un efecto recursivo y si están mal definidas bloquear la base de datos al caer en un bucle infinito

Actualización de vistas

Por norma general una vista no puede actualizarse, sin embargo un sistema gestor de bases de datos puede permitir hacerlo en casos concretos

Hay que consultar el manual del sistema gestor de bases de datos para saber en qué casos concretos lo permite y cómo lo resuelve

En el caso de que se permita su actualización, la vista debe cumplir:

  • La cláusula FROM sólo tiene una relación
  • La cláusula SELECT sólo contiene nombres de atributos de la relación, sin alias, valores agregados y sin la especificación DISTINCT
  • Cualquier atributo que no aparece en la cláusula SELECT deberá ser definido como nulo
  • La consulta no debe contener funciones de agregación (Cláusula GROUP BY, Cláusula HAVING)

DROP VIEW

Se puede utilizar el comando DROP VIEW para borrar una vista

Y sólo de esa vista, ya que no es capaz de trabajar sobre varias

La forma más habitual de consulta de borrado de vistas será:

\text{DROP VIEW v}

Donde v es el nombre de la vista

En el ejemplo se ha borrado la vista llamada todas_las_cuentas

Cláusula WITH

La cláusula WITH fue introducida en la norma SQL:1999 y permite definir una vista temporal

Esta vista temporal sólo puede ser usada en la consulta en la cuál ha sido definida

La forma más habitual será:

\text{WITH} v \text{AS} r_0\newline\\ \text{SELECT} A_1, \cdots, A_2, \cdots, A_n\\ \text{FROM} r_0, r_1, r_2, \cdots, r_m, r\\ \text{WHERE} P

Donde cada Ai representa a un atributo, cada ri una relación (r0 es una consulta), P es un predicado y v es el nombre de la vista temporal

Este ejemplo es equivalente a la vista que vimos en otro ejemplo anterior

Solo que en esta ocasión no hemos usado una vista definida en la base de datos

Hemos usado una vista temporal definida con la cláusula WITH

Transacciones

Transacciones

Las transacciones son una secuencia de instrucciones de consulta o de actualización

La norma SQL especifica que una transacción comienza implícitamente cuando se ejecuta una instrucción SQL

Al finalizar la instrucción debe ejecutarse una de las siguientes instrucciones SQL:

  • COMMIT WORK

    Valida las actualizaciones realizadas hasta ese momento y hace que pasen a ser permanentes en la base de datos

    Una vez completada, se iniciará de forma automática la siguiente transacción

    En el ejemplo hemos creado una relación llamada cuenta y la hemos poblado con 7 tuplas

    Las instrucciones de consulta o actualización están separadas unas de otras por el símbolo ; indicando a SQL que es una nueva instrucción

    Al terminar hemos ejecutado COMMIT para que los cambios se guarden en la base de datos

  • ROLLBACK WORK

    Provoca la anulación de la transacción actual, es decir, anula todas las operaciones realizadas en la base de datos

    La base de datos vuelve al estado inicial, antes de la primera instrucción de la transacción

    En el ejemplo hemos creado una relación llamada cuenta y la hemos poblado con 7 tuplas

    Las instrucciones de consulta o actualización están separadas unas de otras por el símbolo ; indicando a SQL que es una nueva instrucción

    Al terminar hemos ejecutado ROLLBACK para que los cambios no se guardasen en la base de datos

La palabra clave WORK es opcional en ambas instrucciones, por eso hemos podido omitirla en los ejemplos

Utilizar ROLLBACK es útil cuando se detecta un error y es necesario volver a un estado estable de la base de datos

Una vez que se ha ejecutado COMMIT, no es posible realizar un ROLLBACK, ya que los cambios en los datos se hacen permanentes

Por eso, cuando los sistemas de bases de datos detectan un fallo (ya sea una caída de tensión, una caída del sistema, una mala conexión en la red), internamente realizan un ROLLBACK se deniega el uso del COMMIT, para evitar la pérdida de información

En el caso especial de la caída del sistema, el ROLLBACK se realizará al reinicio del sistema

A partir de la norma SQL:1999 se puede encerrar el contenido de una transacción con el siguiente formato:

\text{BEGIN}\\ S_1;\\ S_2;\\ \cdots;\\ S_n;\\ \text{END}

Donde Si es una instrucción de consulta o una actualización

El ejemplo es muy similar al mostrado anteriormente para el COMMIT

Sin embargo, en esta ocasión hemos encerrado la transacción con el formato de la norma SQL:1999

De esta forma podremos distinguir dónde empiezan y terminan distintas transacciones sin tener que buscar las instrucciones COMMIT ó ROLLBACK

Java

Java

Java es un lenguaje puramente orientado a objetos, todo el código debe estar incluido en alguna clase

Fue definido por la empresa Sun Microsystems, que fue adquirida en el año 2009 por Oracle Corporation, anteriormente parte de Silicon Valley, fabricante de semiconductores y software; pero el lenguaje sigue siendo de dominio público. En particular, Oracle ofrece en su página web, de forma gratuita, un JDK (Java Development Kit) el cual permite compilar, depurar y ejecutar programas Java

Si tenemos un programa en un fichero programa.java, podremos compilarlo desde la línea de comandos con:

Esto genera una colección de ficheros de extensión .class, uno por cada clase que aparezca en programa.java y con el nombre de la clase correspondiente

Solo en una de las clases (en el entorno activo) debe tener un método llamado main (el método principal); supongamos que está en la clase programa, podremos ejecutar el programa desde la línea de comandos:

En el caso anterior, la extensión seria opcional

Para que esto funcione correctamente, debemos tener instalado el JDK y hay que asegurarse de que la trayectoria almacenada en la variable CLASSPATH esté en la trayectoria por defecto del sistema

Normalmente los programas se desarrollan en un entorno integral de programación que contiene además de la funcionalidad del JDK, al menos un editor de programas. Sun ofrece en su página web un entorno integrado para Java: Netbeans

El resultado de la compilación de un programa java (los ficheros .class) está expresado en un lenguaje intermedio llamado bytecode

El bytecode es interpretado por la máquina virtual de Java (JVM); la cual es un programa que es invocado por la orden java de la línea de comandos

Los distintos navegadores modernos incorporan una JVM, de modo que pueden ejecutar programas Java desde el navegador, a través de Applets incrustados dentro de las páginas html

Aunque el navegador de Google Chrome ha restringido su uso por seguridad y el JVM no está incluido por defecto, en caso de que el usuario deseara usarlas, debería instalar un pluging de terceros o utilizar un servidor dedicado para aplicaciones Java como por ejemplo Tomcat

Diferencias entre Java y C++

La sintaxis de Java está inspirada en la de C y es superficialmente muy similar a C++ por esa causa. Pero también tiene importantes diferencias

Modos de acceso de Java:

  • Cada atributo o método tiene asociado un permiso
  • Además de public y private (ambos sin los dos puntos) está el modo por defecto package

En cambio en C++, el modo de acceso por defecto es privado

Para utilizar algo por defecto en Java se usan los package (paquetes), que es una simulación de la noción de namespace de C++

Para crear o extender un paquete utilizando la orden package:

  • Debe encontrarse en la 1ª línea del fichero fuente (todo aquello que se encuentre en ese fichero pertenecerá al paquete)
  • Irá seguido del nombre del paquete que se desee utilizar

Si se quieren utilizar desde un paquete clases, atributos, métodos de otro paquete, se utiliza la orden import (es muy similar al using namespaces de C++)

Si el programador no define ningún package explícitamente, el sistema crea uno por defecto sin nombre, que contendrá todos los nombres de los ficheros (que no tengan package explícito) que pertenezcan a ese directorio del sistema operativo

En conclusión, en caso de ausencia de packages explícitos, el modo de acceso por defecto a una clase, un atributo o un método, es accesible desde todos los ficheros de un mismo directorio

Las clases en Java también tienen modo de acceso: pública, privada o por defecto

Si encontramos public al definir una clase, tiene dos consecuencias:

  • Es accesible desde cualquier punto, independientemente de los packages
  • Su nombre debe ser el del fichero .java que lo contiene; sólo puede haber una clase pública por fichero

Comentarios

Java tiene tres tipos de comentarios:

  • El // comenta hasta el final de la línea
  • El /* comenta hasta el primer */ (no se permite el anidamiento)
  • El /** comenta hasta el primer **/

Paquetes

Paquetes

A medida que se incorporan clases de terceros aumenta la posibilidad de encontrarse clases con el mismo nombre

Las posibilidades son aún mayores en Java, donde las clases de millones de programadores viajan a través de la red

La solución que aporta Java son los paquetes (packages)

Para especificar la clase deseada basta con indicar el nombre del paquete al que pertenece

Sun dió en su día unas recomendaciones para que los nombres de los paquetes fueran únicos:

  • Si se va a utilizar dentro de la empresa podrán usar cualquier convenio interno

  • Si se va a utilizar fuera de la empresa deberá usarse como prefijo el dominio de Internet de la compañía invertido (esto asegura que sea único), por ejemplo com.sun.graphics3D

  • Las clases estándar tienen reservado los prefijos java y javax

Toda clase debe pertenecer a algún paquete

Las clases Java se reparten en varios paquetes básicos:

Package
java javax
java.applet javax.accessibility
java.awt javax.activation
java.beans javax.activity
java.io javax.annotation
java.lang javax.crypto
java.math javax.imageio
java.net javax.jws
java.nio javax.lang
java.rmi javax.management
java.security javax.naming
java.sql javax.net
java.text javax.print
java.util javax.rmi
javax.script
javax.security
javax.smartcardio
javax.sound
javax.sql
javax.swing
javax.tools
javax.transaction
javax.xml

El nombre de una clase incluye un prefijo el paquete al que pertenece

En el ejemplo, la clase Vector se encuentra dentro del paquete java.util

En el ejemplo se ha utilizado la palabra reservada import que permite indicar el paquete al que pertenece una o más clases, de tal manera que no haya que repetirlo con cada uso de las mismas

import no produce la carga de ningún tipo de fichero (no equivale al #include de C / C++)

El paquete java.lang es importado automáticamente

Colisión de nombres

En el caso de que se repita el nombre de una clase en dos paquetes importados habría que utilizar el nombre completo de la clase para deshacer la ambigüedad

En el ejemplo se ha especificado el paquete del que se desea coger la clase Vector, con el símbolo asterisco le estamos diciendo que tome todo el paquete

En el ejemplo se ha usado un import de la clase concreta Vector y otro de todo el paquete

Hay que tener en cuenta que el import de la clase tendrá prioridad sobre el import de todo un paquete

Por lo que podría eliminarse si no queremos usar el resto de las clases de ese paquete, al ser redundante

Creación de paquetes

Para indicar que una clase pertenece a un paquete hay que usar la cláusula package

En el ejemplo se ha creado el paquete ejemplos que contiene sólo la clase Ejemplo1

Si hemos de utilizar en otra clase, el nombre del paquete podrá estar formado por varias palabras separadas por punto

En este caso el nombre de la clase será ejemplos.Ejemplo1

Si quisiéramos ejecutarla para usarla en otras clases desde línea de comandos:

Classpath

Java no tiene el concepto de fichero ejecutable (fichero con extensión .EXE o .COM)

Cada clase genera un fichero class independiente

Para ejecutar un programa hay que indicar el nombre de una clase que contenga al menos una función main

El resto de clases se irán cargando dinámicamente bajo demanda

Puede haber varias clases con main pero sólo puede haber un main que sea el principal

En el caso de aplicaciones locales se localizará el código de las clases:

  • La JVM añade al nombre de la clase la extensión class para formar el nombre del fichero

  • Para localizar dicho fichero se utiliza la variable de entorno CLASSPATH

  • CLASSPATH indica una serie de localizaciones en las cuales buscar las clases que pueden ser:

    • Un directorio
    • Un fichero ZIP o JAR (Java Archive Resource)

En el ejemplo se ha usado la línea de comandos de Windows para definir el CLASSPATH

La clase Prueba buscaría (en este orden) en:

  1. c:\ejemplos\Prueba.class
  2. dentro del fichero
  3. c:\java\lib\classes.zip y en ningún lugar más

Cuando se utilizan clases hechas por terceros es necesario incluir su directorio (o ZIP, JAR) en el CLASSPATH para que la JVM se capaz de encontrarlas

Paquetes y Classpath

En el JDK hay relación entre el paquete de una clase y el directorio en el que se encuentra

Todas las clases de un paquete deberán estar en un directorio con el mismo nombre

Si el nombre de un paquete tiene varias palabras cada palabra se corresponde con el nombre de un subdirectorio

Por ejemplo, las clases del paquete ur.mis.ejemplos estarían en el directorio ur/mis/ejemplos

Este directorio deberá ser accesible a partir de alguna de las localizaciones del CLASSPATH

El mecanismo de la JVM del JDK para localizar la implementación de una clase en tiempo de ejecución es:

  1. añade .class al nombre completo de la clase
  2. transforma los puntos en separadores de directorio \
  3. añade la cadena resultante a cada una de las localizaciones del CLASSPATH
  4. si no se encuentra en ninguna de ellas se produce un error de ejecución

Sigamos el proceso con un ejemplo en el que queremos cargar en el JDK la clase UR.ejemplos.ejemplo1:

  1. Definimos el CLASSPATH como:

  2. La JVM busca la clase en el fichero UR\ejemplos\ejemplo1.class

  3. El fichero será buscado en las localizaciones del CLASSPATH c:\programas\UR\ejemplos\ejemplo1.class y c:\temp\UR\ejemplos\ejemplo1.class

  4. Si la clase no se encuentra en ninguno de estos dos sitios, se producirá un error de ejecución

Tipos primitivos en Java

Tipos primitivos

Los tipos primitivos se dividen en:

  • Caracteres
    • char
    • String
  • boolean
  • Numéricos
    • Enteros
      • byte
      • short
      • int
      • long
    • Reales
      • float
      • double
  • Arrays

Los tipos primitivos se caracterizan por tener definidos un mismo tamaño en todas las plataformas (a diferencia del C / C++)

Los literales reales son de tipo double a menos que se incluya el sufijo F

3.14 no es del mismo tipo que 3.14F, el primero será double y el segundo float

Cuando se desea provocar una conversión se utiliza el operador cast

Tiene la siguiente sintáxis:

Usando el cast se pueden realizar aquellas conversiones que el compilador no realiza por defecto

En el ejemplo se ha realizado un casting del double 5.4 a int para guardarlo en la variable i

El resultado de esa operación será que el compilador tomará sólo la parte entera del double, es decir, guardará en i el valor 5

En la segunda operación del ejemplo se divide 5 entre 2, dicha operación devolvería su resultado como un double, pero si el resultado no tendría decimales, se guardaría como un entero

Al hacer un casting en el denominador, forzamos al compilador a que guarde el resultado de la división como un double aunque no tenga decimales

Hay que tener en cuenta, que un casting no corrige el error de división por 0, debe controlarse mediante el uso de excepciones

Wrappers

Un wapper es una envoltura que se aplica a un tipo primitivo concreto

Hay una clase wrapper asociada a cada tipo primitivo

En el ejemplo se han redefinido los wrappers para int (es Integer) y para double (es Double) para poder añadir los datos primitivos int y double en la clase Vector

Las clases Wrapper tienen una segunda funcionalidad

Se utilizan para ubicar todos aquellos servicios propios de los tipos primitivos que representan

Algunos métodos de wrappers importantes:

Wrappers
Integer Character
MAX_VALUE chaValue()
MIN_VALUE equals(Object)
Integer(int) getType(char)
Integer(String) isDigit(char)
byteValue() isIdentifierIgnorable(char)
doubleValue() isJavaIdentifierPart(char)
equals(Object) isJavaIdentifierStart(char)
floatValue() isJavaLetterOrDigit(char)
intValue() isLetter()
longValue() isLetterOrDigit(char)
parseInt(String) isLowerCase(char)
parseInt(String, int) isUpperCase(char)
shortValue() isWhitespace(char)
toBinaryString(int) toLowerCase(char)
toHexString(int) toString()
toOctalString(int) toUpperCase(char)
toString(int, int) toString(int, int)

Caracteres

Un carácter es un símbolo que sigue el estándar ASCII y que nos sirve para generar los textos de nuestros programas

Una cadena de caracteres es un array de caracteres que permiten guardar textos o frases

char

Es una letra que sigue el estándar ASCII y es la unidad menor de cadena

Su tamaño es de 8 bits

Su valor se podría considerar un entero pequeño ya que su rango es -128 a 127

Los valores char tienen asociada la clase Character

Se inicializa con comillas simples

En el ejemplo se inicializado la variable cad como cadena y como puede apreciarse se ha guardado el símbolo ASCII que corresponde a la letra a mayúscula

String

Los cadenas de caracteres no son un tipo primitivo

Sin embargo Java le ofrece unas clases especiales para su tratamiento

Las cadenas son objetos que se manipulan mediante las clases String y StringBuffer

Se usa String cuando la cadena no va a ser modificada

Se usa StringBuffer cuando se desea manipular la cadena

Se recomienda usar habitualmente String (al ser constantes) por ser más eficientes

Inicialización de un String

Las cadenas van entre comillas dobles y los carácter con comillas simples

Cuando el compilador encuentre un literal de cadena, creará un objeto String con el texto de la misma

En el ejemplo se han inicializado dos variables que podrían ser equivalentes

s1 se ha inicializado usando sólo las comillas dobles

s2 se ha inicializando el constructor de objetos de la clase String pasándole como argumento el valor con comillas dobles

Ambas inicializaciones son válidas y el contenido de las variables s1 y s2 será en ambas Hola

La única diferencia es que al ser objetos distintos, su referencia en memoria será también distinta

Métodos básicos

length

El método length devuelve el número de caracteres de una cadena

En el ejemplo se han pedido a dos cadenas de caracteres su número de caracteres

En la primera se han usado comillas dobles y en la segunda el objeto String s1 del ejemplo anterior

Las dos expresiones anteriores serian equivalentes, devuelven un entero con el número de caracteres de la cadena

charAt

El método charAt devuelve el carácter de la posición especificada

Si el argumento de charAt no es un número entre 0 y length – 1 se producirá una excepción

En el ejemplo se ha creado una función que dados un String str y el char busca, se busca el carácter en esa cadena y si se encuentra, el valor de retorno será mayor que 0

Para lograrlo se ha usado el método charAt al que se le ha pasado el valor de i que representa la posición de carácter leído hasta el momento

Como devuelve el carácter leído en ese momento hemos podido compararlo con el carácter busca que nos pasaron como argumento

Métodos de búsqueda

Los métodos para realizar búsquedas en cadenas devuelven la posición del elemento buscado o -1 si no se ha encontrado:

  • indexOf(int ch)
  • indexOf(int ch, int start)
  • indexOf(String str)
  • indexOf(String, int start)
  • lastIndexOf(int ch)
  • lastIndexOf(int ch, int start)
  • lastIndexOf(String str)
  • lastIndexOf(String str, int start)

En el ejemplo se ha usado cada uno de los métodos y se ha comentado su valor devuelto para poder comprenderlos mejor

Comparación de cadenas

Para comparar cadenas no sirve el operador ==, ya que en realidad estamos comparando objetos

La clase String tiene varios métodos para comparar cadenas

Dos cadenas serán iguales si tienen la misma longitud y los mismos caracteres Unicode (a y á son distintos)

Los métodos más habituales son:

  • equals
  • equalsIgnoreCase

    distingue entre mayúsculas y minúsculas

  • compareTo

    además de permitir averiguar si dos cadenas son iguales, también nos dice cual es mayor de las dos:

    • Igual a 0

      Eran iguales

    • Mayor que 0

      El segundo valor era mayor

    • Menor que 0

      El segundo valor era menor

En el ejemplo se ha usado cada uno de los métodos y se ha comentado su valor devuelto para poder comprenderlos mejor

Métodos para comparar prefijos y sufijos

  • boolean starsWith(String)
  • boolean starsWith(String, int start)
  • boolean endsWith(String)

En el ejemplo se ha usado cada uno de los métodos y se ha comentado su valor devuelto para poder comprenderlos mejor

Extracción de cadenas

Hay una serie de métodos que devuelven una nueva cadena que resulta de manipular la cadena original

La cadena original no se modifica (se crea una nueva instancia de la cadena, es decir, se usa otra posición de memoria)

  • String concat(String)
  • String replace(char, char)
  • String replaceAll(String, String)
  • String substring(int start)
  • String substring(int start, int end)
  • String toLowerCase()
  • String toUpperCase()
  • String trim()

En el ejemplo se han usado algunos de los métodos y se ha comentado su valor devuelto para poder comprenderlos mejor

Conversiones de cadenas

Los tipos primitivos se pueden convertir automáticamente a cadenas

Para convertir las cadenas en tipos primitivos hay que utilizar alguna de las siguientes funciones:

  • boolean new Boolean(String).booleanValue()
  • int Integer.parseInt(String)
  • long Long.parseLong(String)
  • float Float.parseFloat(String)
  • double Double.parseDouble(String)

Otros métodos

  • char[] toCharArray()
  • void getChars(int srcBegin, int srcEnd, char[] dest, int destBegin)
  • int hashCode()
  • String valueOf(boolean)
  • String valueOf(int)
  • String valueOf(long)
  • String valueOf(float)
  • String valueOf(double)
  • String[] split(String)

Como puede verse en el ejemplo, usaremos split para trocear cadenas usando un token, en este caso hemos usado el espacio

Este método es bastante útil para trabajar con ficheros de texto en formato CSV, ya que se suele utilizar el símbolo ; (u otro símbolo de token) para separar las distintas columnas o celdas

StringBuffer

Aunque la clase String es la más habitual no es la adecuada para cadenas que deben modificarse frecuentemente

En el ejemplo se crean cuatro objetos String de los cuales sólo uno va a ser usado, admiracion1 o admiracion2

La clase StringBuffer permite modificar la cadena original sin necesidad de crear objetos intermedios como se hizo en el ejemplo

Inicialización de un StringBuffer

Admite los siguientes constructores:

  • StringBuffer()
  • StringBuffer(int i)
  • StringBuffer(String str)

Métodos de modificación

Admite los siguientes métodos de modificación:

  • append(Objetc obj)
  • append(String str)
  • append(char str[] )
  • append(boolean b)
  • append(int i)
  • append(long l)
  • append(float f)
  • append(double d)
  • append(char ch)
  • insert(int offset, Object obj)
  • insert(int offset, String str)
  • insert(int offset, char str[] )
  • insert(int offset, boolean b)
  • insert(int offset, int i)
  • insert(int offset, long l)
  • insert(int offset, float f)
  • insert(int offset, double d)
  • insert(int offset, char ch)
  • setCharAt(int index, char c)
  • setLength(int longitud)

En el ejemplo se han usado algunos de los métodos y se ha comentado su valor devuelto para poder comprenderlos mejor

Métodos básicos

length

Es similar al de String

charAt

Es similar al de String

toString

Realiza la conversión del BufferString a un String

Operador +

Java permite la concatenación de cadenas mediante el +

En el ejemplo, internamente el compilador utiliza un StringBuffer en vez de un String

Por lo que este ejemplo es totalmente compatible con el anterior

En el ejemplo hemos concatenado la cadena con otro tipo (entero), este último se convierte automáticamente a cadena (el compilador invoca implícitamente el método toString() de la clase StringBuffer)

boolean

Su tamaño es de 1 bit

Su valor se podría considerar un entero pequeño ya que su rango es 0 a 1

Siendo el valor 0 igual a FALSE y 1 igual a TRUE

Los valores boolean tienen asociada la clase Boolean

Métodos básicos

booleanValue

Devuelve el valor del booleano

toString

Convierte el tipo booleano a una cadena

Numéricos

Son todos los tipos que permiten trabajar con números y se pueden distinguir según si son enteros o decimales o según su precisión

Enteros

Son todos los tipos que permiten trabajar con números enteros, hay varios tipos según su precisión y el rango de números que pueden llegar a manejar

byte

Su tamaño es de 8 bits

Su valor se podría considerar un entero pequeño sin signo ya que su rango es 0 a 255

Los valores byte tienen asociada la clase Byte

short

Su tamaño es de 16 bits

Su valor se podría considerar un entero sin signo ya que su rango es 0 a 65535

Los valores short tienen asociada la clase Short

int

Su tamaño es de 16 bits

Su valor se podría considerar un entero con signo ya que su rango es -32768 a 32767

Los valores int tienen asociada la clase Integer

long

Su tamaño es de 32 bits

Su valor se podría considerar un entero grande ya que su rango es -2147483648 a 2147483647

Los valores long tienen asociada la clase Long

Reales

Son todos los tipos que permiten trabajar con números decimales, hay varios tipos según su precisión y el rango de números que pueden llegar a manejar

float

Su tamaño es de 32 bits

Su valor se podría considerar un decimal pequeño ya que su rango es 3,4E-38 a 3,4E+38

Los valores float tienen asociada la clase Float

double

Su tamaño es de 64 bits

Su valor se podría considerar un decimal grande ya que su rango es 1,7E-308 a 1,7E+308

Los valores double tienen asociada la clase Double

Arrays

Un array es una agrupación de elementos del mismo tipo

Los arrays en Java se caracterizan por ser objetos

Por tanto, se necesitará una referencia para manipularlos

En el ejemplo podemos ver que como cualquier otro objeto, se inicializa con new

Para acceder a los elementos del array se usa la notación clásica de arrays (usando un índice)

Si no se especifica el valor máximo del indice, el compilador lo tomará como un array dinámico

Se puede considerar que la notación es equivalente a enviar un mensaje al array para consultar un elemento o modificarlo

El rango de un array está entre 0 y N-1 (siendo N el tamaño del array)

Si se accede a una posición fuera de rango se producirá una excepción

En el ejemplo hemos inicializado el array a para luego poder visualizar todo su contenido por pantalla

Como puede verse, Java permite inicializar el array en su declaración

El operador length permite averiguar el tamaño de un array

En el ejemplo hemos inicializado un array de referencias en su declaración

Como puede verse, ambas declaraciones son equivalentes

Arrays bidimensionales

Un array bidimensional puede tener varias filas, y en cada fila no tiene por qué haber el mismo número de elementos o columnas

En el ejemplo hemos declarar e inicializado la matriz bidimensional de doubles matriz para luego recorrerla y visualizarla por pantalla

La primer fila tiene cuatro elementos {1,2,3,4}

La segunda fila tiene dos elementos {5,6}

La tercera fila tiene seis elementos {7,8,9,10,11,12}

La cuarta fila tiene un elemento {13}

matriz.length nos proporciona el número de filas que en este caso es

matriz[i].length nos proporciona el número de elementos en cada fila

Mostramos los elementos de una fila separados por un tabulador usando la función print

Una vez completada una fila se pasa a la siguiente mediante println

Ejemplo de matriz identidad

Para mostrar la potencia de una matriz bidimensional, en este ejemplo se va a construir una matriz identidad de dimensión 4

Una matriz identidad es aquella cuyos elementos son ceros excepto los de la diagonal principal, i = = j, que son unos

Mediante un doble bucle for recorremos los elementos de la matriz especificando su fila i y su columna j poblando la matriz con 1 o 0 en función de si coincide i y j

Mediante un doble bucle for recorremos los elementos de la matriz especificando su fila i y su columna j

Mostramos los elementos de una fila separados por un tabulador usando la función print

Una vez completada una fila se pasa a la siguiente mediante println

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

Sentencias de control en Java

Sentencias de control

Las sentencias de control son no son más que un conjunto de palabras que indican al intérprete cosas como cuál es la próxima sentencia a ejecutar, cuántas veces evaluar una determinada expresión, etc

Sentencias de selección

Sirven para agrupar declaraciones y sentencias en una sentencia compuesta o bloque, que a partir de ese momento se puede considerar sintácticamente equivalente a una simple

if-else

La expresión if-else permite elegir si se ejecutará la siguiente o siguientes sentencias en función de un valor booleano

Tiene la siguiente sintáxis:

Primero se evalúa la expresión booleana y en caso de que su resultado sea igual TRUE se ejecutara el bloque de sentencias a continuación del if

En caso de que su resultado sea igual FALSE se ejecutara el bloque de sentencias a continuación del else

else es opcional y podemos no usarlo, aunque si queremos tener todas las opciones cubiertas, es recomendable usarlo

Dentro de un bloque de sentencias pueden incluirse if-else adicionales, a esta práctica se la conoce como anidamiento y es muy útil para seguir añadiendo condiciones de comprobación al código

En el ejemplo se han comparado los valores de los enteros contenidos en las variables a, b y c, utilizando if-else anidados, mostrando al usuario el valor más alto por pantalla

else-if

La expresión else-if es una mejora a la expresión if-else, su funcionamiento es similar, sin embargo permite disponer de una selección multiple sin recurrir al anidamiento, aunque dentro del bloque de sentencias seguirá siendo posible realizar anidaciones

Tiene la siguiente sintáxis:

else es opcional y podemos no usarlo, aunque si queremos tener todas las opciones cubiertas, es recomendable usarlo

El código de este ejemplo es muy similar al anterior usado con if-else, sintácticamente son equivalentes, pero se ha conseguido reducir un poco el número de líneas de código

switch

Dado que la selección múltiple es una construcción muy habitual, es más recomendable usar el switch

Pero hay que tener en cuenta que sólo funciona con expresiones enteras, no podemos usar cualquier expresión booleana como con if-else o else-if

Tiene la siguiente sintáxis:

Primero se evalúa la expresión entera y se comprueba si es igual al valor contenido en el primer case, en caso de que su resultado sea igual TRUE se ejecutara el bloque de sentencias a continuación de ese case

La ejecución no finalizará hasta encontrar un break, con lo que finalizaría la ejecución de la expresión switch

break puede omitirse y entonces pasaría a comprobar el siguiente case

En caso de que ningún case tenga valor TRUE, entonces funcionaría de forma similiar al else, ejecutándose el bloque de sentencias a continuación del default

En el ejemplo se ha comprobado qué día de la semana es el valor del entero mostrando el nombre del día y en caso de ser sábado o domingo, mostrando que es un día no lectivo y fin de semana

Este ejemplo sólo funcionaría con valores del 1 al 5, ya que si introducimos cualquier otro valor, nos mostraría por pantalla el valor por defecto, es decir, que es fin de semana

Sentencias de salto

En ocasiones necesitamos salir incondicionalmente de alguna expresión y para ello usaremos las sentencias de salto

break

Esta expresión proporciona la opción de salir incondicionalmente de una expresión while, do-while, for, for-each o switch

En caso de que la expresión esté anidada, lo que hará es salir de la expresión más interna

Tiene la siguiente sintáxis:

Aunque sintácticamente es correcta, su uso hace que el código sea difícil de leer, puesto que está añadiendo un punto de salida no controlado por una expresión booleana

continue

Esta expresión es similar a la expresión break, sin embargo, es menos agresiva

Rompe la ejecución del bloque de sentencias pero se vuelve a evaluar la expresión booleana

No puede usarse en un switch, ya que no tendría efecto alguno

En caso de que la expresión esté anidada, lo que hará es salir de la expresión más interna

Tiene la siguiente sintáxis:

Sentencias de iteración

En ocasiones necesitamos ejecutar un bloque de sentencias un determinado o indeterminado número de veces, a estas sentencias se las denomina bucles

while

Esta expresión repite un bloque de sentencias mientras se cumpla una expresión booleana

Tiene la siguiente sintáxis:

Dentro del bloque de sentencias debemos asegurarnos de que se modifica de alguna manera el valor de la expresión booleana, ya que sino podemos caer en un bucle que no termina nunca y que se denomina bucle infinito

Esta situación no es deseable, ya que nuestro programa quedará colgado y es probable que el usuario no sabría qué hacer en esa situación, ya que el programador no se ha molestado en controlarla

En el ejemplo se recorren los números del 1 al 100 y se muestran al usuario por pantalla

Se ha empezado el contador en 0, porque es un convenio que se utilizaba para inicializar variables en C y se sigue utilizando en Java

Igual que utilizar los nombres de variable i, k o j para contadores

do-while

Esta expresión es similar al while, sin embargo siempre se ejecuta el bloque de sentencias al menos una vez y la comprobación de la condición se realiza después de esa ejecución

Tiene la siguiente sintáxis:

Dentro del bloque de sentencias debemos asegurarnos de que se modifica de alguna manera el valor de la expresión booleana, ya que sino podemos caer en un bucle infinito

También admite las expresiones break y continue

En el ejemplo se recorren los números del 1 al 100 y se muestran al usuario por pantalla

Hay que tener en cuenta que la primera vez no se evalúa la expresión booleana, sino fuera por esa situación, sería totalmente equivalente a la expresión while

for

Esta expresión es similar al while, sin embargo permite inicializar la variable que controlará el bucle

Tiene la siguiente sintáxis:

Dentro de la variación de la variable debemos asegurarnos de que se modifica de alguna manera el valor de la expresión booleana, ya que sino podemos caer en un bucle infinito

También admite las expresiones break y continue aunque se desaconseja su uso, ya que se considera que para esta expresión rompen el flujo normal del programa

En el ejemplo se recorren los números del 1 al 100 y se muestran al usuario por pantalla

Es totalmente equivalente a la expresión while, pero ahorra algunas líneas de código al incluir su propia inicialización y en este caso, un incremento de la variable fuera del bloque de expresiones

for-each

Esta expresión es similar al for pero está especializada en recorrer objetos que admitan la clase Iterator

Fue introducido en la versión 5 de Java

Tiene la siguiente sintáxis:

No es necesario asegurarnos de que se modifica de alguna manera el valor de la expresión booleana como en un for, ya que al basarse en la clase Iterator, el bucle terminará cuando no queden más elementos del objeto, impidiendo de esta forma caer en un bucle infinito

También admite las expresiones break y continue aunque se desaconseja su uso, ya que se considera que para esta expresión rompen el flujo normal del programa

En el ejemplo se recorre un array que contiene los números del 1 al 5 y se muestran al usuario por pantalla

En este caso se en ha recorrido un sencillo array, pero podrían recorrerse objetos más especializados (siempre que admitan la clase Iterator) como un ArrayList, un Vector, un Map, etc

Excepciones en Java

Excepciones en Java

Si una operación no puede completarse debido a un error, el programa deberá:

  • volver a un estado estable y permitir otras operaciones

  • intentar guardar el trabajo y finalizar

Esta tarea es difícil debido a que generalmente el código que detecta el error no es el que puede realizar dichas tareas por eso debe informar al que pueda manejarlo

La solución más habitual son los códigos de error

Java ofrece otra forma de tratar con los errores en condiciones excepcionales: las excepciones

Una condición excepcional es aquella que impide la continuación de una operación

No se sabe como manejarla, pero no se puede continuar

En Java se lanza una excepción para que alguien que sepa manejarla la trate en un contexto superior

En el ejemplo se comprueba de forma condicional si el fichero readme.txt existía en el sistema y en caso de no existir se lanza la excepción IOException que de momento está sin tratar

La palabra reservada throw finaliza el método actual y lanza un objeto que facilite información sobre el error ocurrido

Normalmente se utilizará una clase para cada tipo de error

Java obliga a que un método informe de las excepciones explícitas que pueda lanzar

Un método no solo tiene que decir que devuelve si todo va bien; debe indicar también qué puede fallar

En el ejemplo se han declarado las excepciones EOFException y FileNotFoundException que afectaban al método fichero

La especificación de excepciones se realiza mediante la palabra throws en la declaración del método y puede haber tantas como necesitemos separadas por un espacio

Manejo de la excepciones

Una vez detectado el error hace falta indicar quien se encarga de tratarlo

Un manejador de excepciones tiene el siguiente formato:

El bloque try delimita el grupo de operaciones que puede producir excepciones

El bloque catch es el lugar al que se transfiere el control si alguna de las operaciones produce una excepción

En el ejemplo se ha usado un bloque try con su bloque catch para gestionar los errores del ejemplo anterior

Si alguna de las operaciones del bloque produce una excepción, se interrumpe el bloque try y se ejecuta el catch

Al finalizar este, se continua normalmente

Si no se produce ninguna excepción, el bloque catch se ignora

También existe la posibilidad de utilizar la excepción genérica Exception, pero es más recomendable usar el tipo de excepción específica para el error

En el ejemplo se ha usado un bloque try con su bloque catch para gestionar varias excepciones como en el ejemplo anterior

Un bloque try puede tener varios catch asociados

Cuando una excepción no se corresponde con ningún catch se propaga hacia atrás en la secuencia de invocaciones hasta encontrar un catch adecuado

En el ejemplo se gestionan las excepciones de los métodos f1 y f2 y si no encuentran ninguna continua la ejecución del código de forma normal fuera del bloque try

En este ejemplo se han tratado de mostrar las 3 alternativas que podemos usar para tratar la excepción IOException desde el método f2

Cuando se invoca un método que lanza excepciones es obligatorio:

  • que se maneje el error con un bloque catch

  • que se indique mediante throws su propagación

Bloque finally

Puede haber ocasiones en que se desea realizar alguna operación tanto si se producen excepciones como si no

Dichas operaciones se pueden situar dentro de un bloque finally

Un bloque finally tiene el siguiente formato:

Si no hay ninguna excepción se ejecutará el bloque try y el finally

En el ejemplo se ha controlado el cierre del archivo f en el bloque finaly, ejecutándose siempre

Si se produce alguna excepción:

  • Si es atrapada por un catch del mismo try se ejecuta este y luego el finally

    La ejecución continua después del finally normalmente

  • Si no es atrapada se ejecuta el finally y la excepción se propaga al contexto anterior

Jerarquía de las excepciones

Toda excepción debe ser una instancia de la clase de Throwable

De ella hereda los siguientes métodos:

  • getMessage()

    devuelve una cadena con un mensaje que detalla de detalla excepción

  • toString()

    devuelve una descripción breve que es el resultado es concatenar:

    • el nombre de la clase de este objeto
    • el token :
    • el resultado del método getLocalizedMessage() de este objeto
  • fillInStackTrace()

    registra información sobre el estado actual de los marcos de pila para el subproceso actual

    Si el estado de la pila no se puede escribir, no habrá valor devuelto

  • printStackTrace()

    devuelve la traza que ha seguido la excepción

    La primera línea de salida contiene el resultado del método toString() de este objeto

    Las líneas restantes representan los datos registrados previamente por el método fillInStackTrace()

La base de la jerarquía de excepciones de Java es:

  • Throwable

    • Error

    • Exception

      • RuntimeException

Las clases derivadas de Error describen errores internos de la JVM:

  • no deben ser lanzados por las clases de usuario

  • estas excepciones rara vez ocurren y cuando así sea lo único que se puede hacer es intentar cerrar el programa sin perder los datos

  • Ejemplos: OutOfMemoryError; StackOverflow; etc

Los programas en Java trabajarán con las excepciones de la rama Exception

Este grupo se divide a su vez en:

  • Las clases que derivan directamente de Exception (explícitas)

  • Las que derivan de RuntimeException (implícitas)

Se utilizan las RunTimeException para indicar un error de programación

Si se produce una excepción de este tipo hay que arreglar el código

Ejemplos:

  • Un cast incorrecto

  • Acceso a un array fuera de rango

  • Uso de un puntero null

El resto de Exception indican que ha ocurrido algún error debido a alguna causa ajena al programa

Ejemplos:

  • Un error de E/S

  • Error de conexión

Declaración de excepciones

Los métodos deben declarar sólo las excepciones explícitas

Las implícitas no deben declararse (aunque pueden producirse igualmente)

Por tanto cuando un método declara una excepción avisa de que puede producirse dicho error además de cualquier error implícito

Creación de excepciones

Si se necesita gestionar algún error no contemplado por los estándar que dispone Java, podemos crear nuestra propia clase de excepción

La única condición es que la clase derive de Throwable o de alguna que derive de ella

En la práctica generalmente se derivarán:

  • de RunTimeException si se desea notificar un error de programación

  • de Exception en cualquier otro caso

Resumen

Excepciones explícitas Excepciones implícitas
Derivan de Exception (no de RunTimeException) Derivan de RunTimeException
Indican un error externo a la aplicación Indican un error de programación
Si se lanzan es obligatorio declararlas No se declarar: se corrigen
Si se invoca un método que las lanza es obligatorio atraparlas o declararlas

Se pueden atrapar

En caso contrario, finaliza la aplicación

Excepciones y herencia

Al redefinir un método se está dando otra implementación para un mismo comportamiento

El nuevo método sólo podrá lanzar las excepciones declaradas en el método original

En el ejemplo se ha redefinido el método f de la clase A en la clase B con una excepción adicional

Un método puede declarar excepciones que no lance realmente

Así un método base puede permitir que las redefiniciones puedan producir excepciones

Los constructores, al no heredarse, pueden lanzar nuevas excepciones

Normas de uso

  1. Norma

    • Si una excepción se puede manejar no debe propagarse

    • Es más cómodo usar métodos que no produzcan errores

  2. Norma

    • No utilizar las excepciones para evitar una consulta

      • No abusar de ellas

  3. Norma

    • Separar el tratamiento de errores de la lógica

      Todo junto
      Separado
  4. Norma

    • No ignorar una excepción ya que dejamos el código con errores sin controlar

Flujos de E/S en Java

Flujos de E/S

Los flujos de E/S en Java se realizan a través de secuencias ordenadas de bytes (streams)

Estos pueden ser de entrada (InputStream) o de salida (OutputStream) independizando a los objetos de los datos

Las clases de E/S se encuentran en el paquete java.io

Los métodos de OutputStream y de InputStream lanzan la excepción IOException ante cualquier fallo relacionado con el intento de lectura/escritura

La clase OutputStream

Clase abstracta de la que derivan los demás flujos de salida

Fundamentalmente ofrece los siguientes métodos:

  • public abstract void write(int b) throws IOException
  • public void write(byte b[]) throws IOException
  • public void write(byte b[], int off, int len) throws IOException
  • public void flush() throws IOException

    Fuerza la escritura

  • public void close() throws IOException

Hay varias clases que derivan de OutputStream y redefinen el método write para darle una implementación distinta:

  • FileOutputStream

    redefine el método write para enviar bytes a un fichero

  • ByteArrayOutputStream
  • PipedOutputStream
  • Sun.net.TelnetOutputStream

FilterOutputStream

Un filtro de salida es un OutputStream al cual se le asocia en su constructor otro OutputStream (un patrón decorador)

Los filtros reenvían toda la información que reciben a su OutputStream asociado, previa realización de algún tipo de transformación en la misma

De esta manera cada filtro añade una funcionalidad adicional al OutputStream que encapsula

Se pueden encadenar varios filtros para obtener varias funcionalidades combinadas

En el ejemplo se ha cargado el archivo datos.dat para escritura y utilizando el método write se han guardado varios valores enteros que se almacenaban en la variable i

Se le añade & 0xFF al final del flujo de escritura para indicar dentro del fichero, que se ha terminado de escribir un dato

Filtro DataOutputStream

Los métodos de la clase OutputStream solo permiten enviar bytes

Cualquier otro tipo deberá descomponerse en una secuencia de bytes antes de poder escribirse

Entre los métodos más destacados:

  • void writeBytes(String s) throws IOException
  • void writeBoolean(boolean v) throws IOException
  • void writeShort(int v) throws IOException
  • void writeChar(int v) throws IOException
  • void writeInt(int v) throws IOException
  • void writeLong(long v) throws IOException
  • void writeFloat(float v) throws IOException
  • void writeDouble(double v) throws IOException
  • void writeChars(Strings s) throws IOException

    Casi no usado, es más usado el filtro PrintStream

En el ejemplo se han usado dos filtros, el FileOutputStream visto anteriormente y el DataOutputStream visto ahora

La clase DataOutputStream añade la funcionalidad de poder enviar todos los tipos primitivos directamente

Así que podremos escribir en el fichero cualquier tipo primitivo e incluso atributos de un objeto, siempre que sean tipos primitivos, ya que no escribe el objeto completo

Filtro PrintStream

La clase PrintStream añade la funcionalidad de poder enviar todos los tipos primitivos en formato de texto

En el ejemplo se han usado dos filtros, el FileOutputStream visto anteriormente y el PrintStream visto ahora

Los métodos print y println están sobrecargados para todos los tipos primitivos

System.out es de tipo PrintStream y por eso no es necesario referenciarlo al usar este filtro

La forma más habitual de usar OutputStream será combinando varios filtros

La clase InputStream

Clase abstracta de la que derivan los demás flujos de entrada

Fundamentalmente ofrece los siguientes métodos:

  • public abstract int read() throws IOException
  • public int read(byte b[]) throws IOException
  • public int read(byte b[], int off, int len) throws IOException
  • public int available() throws IOException
  • public void close() throws IOException

Hay varias clases que derivan de InputStream y redifinen el método read para darle una implementación distinta:

  • FileInputStream

    redefine el método read para leer bytes de un fichero

  • ByteArrayInputStream
  • StringInputStream
  • SequenceInputStream
  • PipedInputStream
  • Sun.net.TelnetInputStream

FilterInputStream

Un filtro de entrada es un InputStream al cual se le asocia en su constructor otro InputStream

Los filtros devuelven la información que a su vez han leído de su InputStream asociado, previa realización de algún tipo de transformación en la misma

De esta manera cada filtro añade una funcionalidad adicional al InputStream básico

Se pueden encadenar varios filtros para obtener varias funcionalidades combinadas

En el ejemplo se ha cargado el archivo datos.dat para lectura y utilizando el método read se han leido varios valores enteros que se almacenaban en la variable ch en las posiciones i + 1 y los hemos mostrado por pantalla

Así hemos podido leer el archivo que escribimos con el ejemplo de FileOutputStream

Filtro DataInputStream

Los métodos de la clase InputStream solo permiten leer bytes

Cualquier otro tipo deberán leerse como una secuencia de bytes y recomponerse antes de poder utilizarse

Fundamentalmente ofrece los siguientes métodos:

  • readBoolean()
  • readChar()
  • readByte()
  • readShort()
  • readInt()
  • readLong()
  • readFloat()
  • readLong()
  • readFloat()
  • readDouble()
  • readLine()
  • readFully(byte[] b)

En el ejemplo se han usado dos filtros, el FileInputStream visto anteriormente y el DataInputStream visto ahora

La clase DataInputStream añade la funcionalidad de poder recibir todos los tipos primitivos directamente

Lo que nos ha permitido mostrar datos primitivos contenidos en el fichero directamente en la pantalla

Así que leer del fichero cualquier tipo primitivo e incluso atributos de un objeto, siempre que sean tipos primitivos, ya que no lee el objeto completo

La forma más habitual de usar InputStream será combinando varios filtros

Utilidades en Java

Utilidades

Existe un conjunto de utilidades dentro del lenguaje Java

Se encuentran en el paquete java.util

Son clases útiles y de uso cotidiano, entre las que destacamos:

  • Colecciones
    • Collection
      • List
      • Set
      • Map
  • Fechas
    • Date
    • Calendar
  • Properties
  • Scanner

Collection

El API de colecciones de Java incluye clases que son capaces de albergar conjuntos de objetos

Representan contenedores de objetos

Son estructuras dinámicas, crecen a medida que se añaden elementos y trabajan con Objects

  • Se puede añadir cualquier cosa

  • Al recuperar el elemento se debe hacer casting

Básicamente hay dos conjuntos:

  • Clases que implementan Collection

    • Representan contenedores de objetos

    • Básicamente hay listas (List) y conjuntos (Set)

  • Clases que implementan Map

Algunos de sus métodos más importantes son:

  • boolean add(Object o)

    añade un elemento a la colección

  • boolean addAll(Collection c)

    añade un grupo de objetos (otra colección) a la colección

  • void clear()

    vacía la colección

  • boolean contains(Object o)

    chequea si un elemento se encuentra dentro de la colección

  • boolean isEmpty()

    chequea si la colección está vacía

  • Iterator iterator()

    devuelve un java.util.Iterador para recorrer los elementos de la colección

  • boolean remove(Object o)

    quita un elemento de la colección

  • int size()

    número de objetos almacenados

  • Object[] toArray(Object[] a)

    devuelve un array con los elementos de la colección

List

Sirve como interface para clases que representan listas de datos o vectores dinámicos

Sus elementos son accesibles por medio de un indice

Hereda los métodos de Collection y además añade:

  • void add(int index, Object element)

    añade un elemento en una posición determinada

  • Object get(int index)

    devuelve el elemento que ocupa una posición determinada

  • int indexOf(Object o)

    devuelve el indice de la primera aparición del elemento en la lista (o -1 si no lo encuentra)

  • Object remove(int index)

    elimina el elemento que ocupa una posición determinada

  • Object set(int index, Object element)

    cambia el elemento que ocupa una posición determinada por otro elemento

Al añadir un objeto con add(Object), el elemento se añade al final

Al eliminar un objeto con remove(Object), todos los demás se desplazan para no dejar huecos

Los objetos List más habituales son Vector (es threadsafe) y ArrayList (no es threadsafe)

Set

Sirve como interface para clases que representan conjuntos de datos

No pueden tener elementos duplicados

Sus elementos no son accesibles por un indice

No añade métodos nuevos

Los objetos Set más habituales son HashSet y TreeSet

Map

Representan tablas de datos

Todo mapa se compone de un conjunto de entradas compuestas de:

  • Una clave, sirve para recuperar el elemento
  • Un valor

Los métodos más importantes son:

  • void clear()

    vacía la estructura

  • boolean containsKey(Object key)

    devuelve true si hay algún objeto almacenado con esa clave

  • boolean containsKey(Object value)

    devuelve true si el Map contiene ese objeto

  • Object get(Object key)

    permite recuperar un determinado elemento a partir de su clave

  • boolean isEmpty()

    indica si el Map está vacío

  • Set keySet()

    devuelve un conjunto de claves del Map

  • Object put(Object key, Object value)

    almacena un objeto en el mapa con una clase determinada. Si la clave ya esta almacenada se sustituye por un objeto asociado por el nuevo

  • void putAll(Map t)

    incluye un submapa en el mapa

  • Object remove(Object key)

    elimina un objeto de clave determinada

  • int size()

    devuelve el número de objetos almacenados

  • Collection values()

    devuelve el conjunto de valores almacenados en el mapa

Se implementan como tablas Hash

Los más habituales son HashMap (no es threadsafe) y Hashtable (es threadsafe)

Iterator

Hay colecciones a las que se accede por medio de un índice, otras no

Iterator permite recorrer colecciones de objetos independientemente de cómo estén organizadas

Otra ventaja, permite eliminar objetos sin tener que gestionar los indices

Los métodos más importantes son:

  • boolean hasNext()

    devuelve True si el iterador tiene algún elemento más

  • Object next()

    devuelve el siguiente elemento de la iteración

  • void remove()

    elimina de la colección el último elemento devuelto por el iterador

Comparación de recorrido de una Colección
for con índice
Iterador

Fechas

Hay dos clases que permiten trabajar con fechas:

  • Date

    casi todos sus métodos están en desuso por lo que no es muy recomendable su uso

  • Calendar

    es una interface por lo que es muy fácil darle el comportamiento que queramos

Ambas representan internamente el tiempo mediante un valor de tipo long que representa el número de milisegundos desde la el 1 de Enero de 1970 a las 00:00:00 GMT

Ese valor puede ser obtenido usando System.currentTimeMillis()

Calendar permite muchas más operaciones con fechas que Date, aunque es más difícil de construir (al ser una interfaz deberemos hacer nosotros mismos su implementación)

Date

Es fácil de construir:

  • new Date()

    construye un objeto Date con la fecha actual del sistema

Métodos que no están en desuso:

  • boolean after(Date when)

    comprueba si la fecha es posterior a la entregada como parámetro

  • boolean before(Date when)

    comprueba si la fecha es anterior a la entregada como parámetro

  • long getTime()

    devuelve el número de milisegundos desde el 1 de Enero de 1970 a las 00:00:00 GTM

  • void setTime(long time)

    cambia la fecha por otra fecha en milisegundos

Métodos en desuso:

  • int getDay()

    devuelve el día de la semana para esa fecha o devuelve el día que representa ese instante en la zona local

    Día de la semana
    Día Domingo Lunes Martes Miércoles Jueves Viernes Sábado
    Valor 0 1 2 3 4 5 6
  • getMonth()

    devuelve el mes para esa fecha empezando por 0

    Meses
    Mes Enero Febrero Marzo Abril Mayo Junio Julio Agosto Septiembre Octubre Noviembre Diciembre
    Valor 0 1 2 3 4 5 6 7 8 9 10 11
  • getYear()

    devuelve el año para esa fecha a la que se le ha restado 1900

  • getHours()

    devuelve la hora para esa fecha, la cuál estará comprendida entre 0 y 23

  • getMinutes()

    devuelve los minutos para esa fecha, los cuales estarán comprendidos entre 0 y 59

  • getSeconds()

    devuelve los segundos para esa fecha, los cuales estarán comprendidos entre 0 y 61, los valores 60 y 61 sólo aparecerán cuando la MVJ se salte algunos segundos en una cuenta

Calendar

Clase abstracta para representar fechas en distintos tipos de calendarios

Incluye un montón de constantes para todos los días de la semana, meses, campos de una fecha…

En occidente para obtener la fecha del sistema utilizaremos el GregorianCalendar:

  • new GregorianCalendar()
  • new GregorianCalendar(2007, 1, 5)

    Hay que prestar atención a la fecha:

    • es año, mes, día

    • Corresponde al 5 de Febrero de 2007 y no al 5 de Enero de 2007 como podría parecer a primera vista

      Enero corresponde al valor 0

También podemos obtener la fecha del sistema mediante el método estático Calendar.getInstance()

Los métodos más destacable son:

  • void add(int field, int amount)

    suma (o resta, según el signo del operando) tiempo a una fecha

  • boolean after(Object when)

    comprueba si la fecha es posterior a la entregada como parámetro

  • boolean before(Object when)

    comprueba si la fecha es anterior a la entregada como parámetro

  • int get(int field)

    devuelve el valor de algún campo de la fecha, el cual especificamos con el parámetro field

  • Date getTime()

    convierte el tipo Calendar a Date

  • void set(int field, int value)

    cambia de valor un campo de la fecha

  • void set(int year, int month, int date)

    cambia el año, el mes y el día de la fecha

  • void setTime(Date date)

    a partir de un Date, crea un Calendar

Properties

Habitualmente el comportamiento de las aplicaciones puede cambiar en función de parámetros

Es muy cómodo colocar esos parámetros de configuración en ficheros para que la aplicación los lea según los necesite

Java dispone de la clase Properties que automatiza esta gestión

Cada línea del fichero de propiedades guarda una variable y tiene el formato:

Vamos a usar el fichero llamado conexion.properties que tendrá el siguiente contenido:

Para mostrar el uso de Properties con el siguiente ejemplo:

Scanner

Se puede utilizar la clase Scanner para leer tipos primitivos y Strings usando expresiones regulares

La clase Scanner recoge los caracteres pulsados desde el teclado hasta encontrar un delimitador que por defecto es el espacio

Los datos recogidos son convertidos a los diferentes tipos usando el método next<tipo>(), para recoger textos como String usaremos sólamente next()

De esta forma podremos recoger del teclado o de otros medios, los tipos de datos que necesitemos

En el ejemplo hemos leído un int desde el teclado

En el ejemplo hemos leído varios long desde un fichero llamado numeros.txt

En el ejemplo hemos leído varias palabras desde un String

Mostrándonos por pantalla:

Es decir, las palabras que contenía el String, cada una en una línea cada vez porque usamos println para mostrarlas por pantalla

Como podemos ver, si en cambio, hubiésemos utilizado print las habría mostrado todas en la misma línea sin ningún delimitador entre ellas

En el ejemplo hemos leído varias palabras desde un String cambiando el delimitador espacio por el delimitador 1

Para definir el nuevo delimitador usamos el método useDelimiter y le pasamos de parámetro una cadena que contiene una expresión regular, en este caso \\s*1\\s*

Mostrando por pantalla lo mismo que en el ejemplo anterior a pesar de usar un delimitador distinto