Archivo de la categoría: Lenguaje de programación

Lenguaje de programación es un lenguaje formal diseñado para realizar procesos que pueden ser llevados a cabo por máquinas

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

JDBC

JDBC

JDBC es un conjunto de clases e interfaces Java para la ejecución de sentencias SQL

Es la parte CLI de Java (Call-Level Interface)

Fue desarrollado conjuntamente por JavaSoft, Sybase, Informix e IBM entre otros

JDBC permite la manipulación de cualquier base de datos SQL

No es necesario hacer un programa específico para manipular Oracle, Sybase, etc…

Nuestro programa puede manipular cualquier base de datos

Uniendo Java con JDBC obtenemos programas que se pueden ejecutar en cualquier plataforma y que pueden manipular cualquier base de datos

Las clases e interfaces JDBC se encuentran dentro del paquete java.sql

Proceso de trabajo con JDBC

El proceso de trabajo en JDBC consiste en los siguientes pasos:

  1. Conectarse a la base de datos (mediante DriverManager)

  2. Emitir sentencias SQL (mediante Statement, PreparedStatement, CallableStatement)

  3. Procesamiento de los resultados (ResultSet)

Driver JDBC

Cada base de datos concreta implementa la interface JDBC de una manera particular, al igual que las clases auxiliares y de utilidad que sean necesarias para esa base de datos

Por eso son necesarios los drivers de bases de datos

Una misma aplicación podrá conectar con distintas bases de datos simplemente cambiando ese driver (no debemos confundir el driver de la base de datos con la clase Driver, pues son cosas distintas)

El interface Driver especifica los métodos que todo driver JDBC debe implementar, para ello se pueden cargar los drivers de dos maneras:

  1. Cuando se inicia la clase Driver, el DriverManager consulta la propiedad jdbc.drivers

    Dicha propiedad contiene una lista de drivers (las clases) que deben ser cargadas

    Para ello deberá ejecutarse un comando como este (para usar ODBC) en la línea de comandos:

  2. Si se desea introducir un nuevo driver después de que el DriverManager se halla inicializado se deberá usar el método forname de la clase Class

    Para ello deberemos incluir en nuestro código la siguiente instrucción (para usar ODBC):

    Es aconsejable que se use de forma static en nuestra aplicación (porque sólo se cargará el driver una vez, al cargar la clase)

Ejemplos de drivers
Base de datos Driver
ODBC sun.jdbc.odbc.JdbcOdbcDriver
Oracle oracle.jdbc.driver.OracleDriver
SQLServer com.microsoft.jdbc.sqlserver.SQLServerDriver
MySQL com.mysql.jdbcDriver

Tipos de driver:

  • Puente JDBC-ODBC

    Traduce JDBC a ODBC y lo retransmite al driver ODBC de la máquina

    Es el driver ODBC el que realmente se comunica con la base de datos

    Está incluido en el JDK pero no incluye JDBC2

    Inconvenientes:

    • Es útil para realizar pruebas, pero es lento en producción

    • Es necesario el driver ODBC en el cliente (menor portabilidad)

  • Driver JDBC sobre driver nativo de la BD

    Retransmite JDBC al driver nativo instalado en la máquina

    El driver nativo es el que realmente se comunica con al base de datos

    Inconveniente:

    • necesidad de driver nativo (menor portabilidad)
  • Driver Java sobre red

    Traduce las llamadas JDBC a un protocolo de red independiente de la plataforma que contacta con el servidor

    El servidor traduce esas peticiones al protocolo concreto de cada base de datos

    Se usa un middleware en el servidor de red que es capaz de conectar a los clientes puros Java a muchas bases de datos diferentes

    Ventaja:

    • es rápido, independiente de la plataforma y no requiere de instalación en el cliente

  • Driver puro Java con protocolo nativo

    Traduce las llamadas JDBC al protocolo específico de la base de datos contactando directamente con ella

Obtener conexiones

Mediante el driver concreto realizaremos las conexiones, pero las solicitudes de conexión deben ser realizadas mediante el DriverManager

Una vez que la clase Driver ha sido cargada y registrada, el DriverManager puede establecer conexiones con la base de datos mediante estos dos pasos:

  1. Se llama al método DriverManager.getConnection(url, user, pass); y se obtiene un objeto de tipo Connection

  2. El DriverManager prueba los drivers registrados para ver si puede establecer la conexión y si no fue posible, lanza una SQLException

Una misma aplicación, puede tener varias conexiones a la misma base de datos o varias conexiones a otras bases de datos (hasta el máximo permitido por la base de datos)

Los parámetros que admite getConnection son la url (que es un subprotocolo que utiliza la base de datos para realizar la conexión), el user (el nombre de usuario que va a conectarse) y el pass (la contraseña que utiliza el usuario para conectarse)

El parametro url tiene el siguiente formato:

El subprotocolo es particular de cada base de datos y lo utiliza el DriverManager para buscar el driver adecuado para manipularla

El subnombre depende del subprotocolo concreto

El driver será el encargado de interpretarlo y le ayudará a localizar la base de datos

Nuestra aplicación no trabajará con el driver concreto, sino que lo hará el DriverManager

De esta manera las aplicaciones pueden trabajar con el objeto Connection sin preocuparse por el tipo de base de datos con la cual estemos trabajando (no hay que hacer modificaciones en el código, solo cambiar el parámetro url)

La conexión debe cerrarse siempre al finalizar, porque sino se consumen recursos de forma innecesaria, aunque se pueden reutilizar conexiones, no es aconsejable, es mejor usar siempre una conexión nueva

En el ejemplo se ha utilizado un esqueleto de conexión para Oracle

Para utilizar la conexión debemos crear un objeto de tipo Connection, el cual representa una sesión abierta con la base de datos

El objeto provee de un contexto con el que poder emitir sentencias SQL y obtener resultados

El objeto Connection debe inicializarse inicialmente a null, así podremos comprobar si hubo conexión porque incluiremos un bloque try para gestionar errores, el cuál lanzará SQLException

Para cerrar la conexión debe hacerse mediante el método close(), dentro de un finaly (el cual también tendrá su bloque try que lanza SQLException)

El interface Statement

La conexión nos permite crear objetos Statement mediante el método createStatemen()

Los objetos Statement permiten la ejecución de sentencias SQL y la obtención de resultados (mediante los objetos ResultSet)

Los métodos para ejecutar SQL aceptan String como parámetro (JDBC es CLI), los cuales pueden componerse dinámicamente en función de valores contenidos en variables

Para componer el String SQL habrá que concatenar los distintos fragmentos SQL (los estáticos y las variables)

Hay tres formas de ejecutar sentencias SQL:

  1. Mediante el método executeQuery(<sql>) para consultas mediante la sentencia SELECT que produce túplas como resultado (devuelve un objeto de tipo ResultSet)

    • Es como una tabla que almacena el resultado de la consulta

    • Tiene una serie de métodos de consulta

    • Es como un cursor (en terminología PL / SQL)

  2. Mediante el método executeUpdate(<sql>) para actualizaciones mediante las sentencias INSERT, DELETE, UPDATE, así como comandos DDL (CREATE, DROP, ALTER TABLE, ADD) y bloques PL / SQL (entre los bloques begin y end)

    • Devuelve un entero que indica el número de filas modificadas por el comando

    • Con los comandos DDL devuelve 0

  3. Mediante el método execute(<sql>) que ejecuta cualquier sentencia SQL

    • Si se utilizó un QUERY, devolverá True

      • El ResultSet se puede obtener mediante el método getResultSet()

    • Si se utilizó un UPDATE, devolverá False

      • El total de filas modificadas se pueden obtener mediante el método getUpdateCount()

Después de procesar el resultado, se debe cerrar el Statement mediante el método close()

Este método cierra también el ResultSet asociado, de todas formas, Sun recomendaba cerrar el ResultSet de forma explícita para evitar errores no deseados

Un mismo Statement puede reutilizarse en una misma conexión para ejecutar distintas sentencias

El interface ResultSet

El objeto ResultSet actúa como un cursor dentro de los resultados

La primera vez que se lee apunta encima del primer resultado, pero no lo lee, por eso hay que leer la primera fila avanzando mediante el método next()

El método next() devuelve True si pudo avanzar o False si no pudo

Para obtener los valores del ResultSet se utilizan los métodos get que tienen el siguiente formato get<tipo>(<columna>)

Para nombrar las columnas podemos o hacerlo por su nombre o por un indice, el cual empieza en 1, su numeración viene dada por el orden en que fue introducida en el SELECT

Hay que prestar atención a las fechas, porque se guardan como java.sql.Date, no como java.util.Date como se podría esperar

Conviene cerrar el ResultSet aunque se cierra implícitamente al cerrar o reutilizar el Statement que lo creó

Cuando se ha leído un null de SQL usando uno de los métodos get<tipo>, éste devuelve:

  • Un valor null de Java para aquellos métodos que devuelven objetos Java (getString(), getBigDecimal(), getDate(), getTime(), getTimestamp(), getObject(), etc)

  • Un valor 0 para aquellos métodos que devuelven tipos numéricos (getByte(), getShort(), getInt(), getLong(), getFloat(), getDouble() )

  • Un valor False para el método getBoolean()

Para determinar si un valor dado era null, primero debe intentar leerse la columna y usar el método de ResulSet wasNull() para saber si fue null

Devolverá True si lo era, False en caso contrario

El interface PreparedStatement

Cada vez que se lanza un Statement la base de datos debe interpretarla y calcular un plan de consulta

Pero al usar PreparedStatement, se puede ejecutar múltiples veces, obteniendo un aumento de rendimiento al tener la consulta ya analizada y optimizada

No todas las bases de datos soportan PreparedStatement, hay que leer la documentación de la misma para saber si se pueden usar

Los objetos PreparedStatement derivan del Statement obtenido de la conexión, mediante el método prepareStatement(<sql>);

Un PreparedStatement sirve para lanzar instrucciones SQL precompiladas, podremos parametrizar una o más entradas mediante el operador ?, cuyos valores podrán ser modificados en distintas ejecuciones de la instrucción

Parametrizar las entradas es útil cuando desconocemos los valores de ciertos tipos de datos SQL en la base de datos de destino,

El valor parametrizado obtiene el valor correcto del driver de la base de datos, por lo que no deberemos preocuparnos por los tipos

Antes de que la instrucción SQL pueda ser ejecutada deberemos asignarle un valor a los parámetros de entrada

Para asignar valores usaremos los métodos set<tipo>(columna, valor); siendo el tipo compatible con el del parámetro

Para ejecutarlo se usan los métodos execute de Statement

En el ejemplo se ha utilizado un PreparedStatement para Oracle

Python

Python

Python es un lenguaje de programación muy utilizado actualmente en entornos relacionados con Data Science y Big Data

Su uso en librerías creadas por Google y otros gigantes tecnológicos ha hecho que en los últimos años se haya convertido en uno de los lenguajes más utilizados

Dentro de los lenguajes de programación, sus características son:

  • Interpretado

    El proceso de compilado es el de traducción del lenguaje de programación que estemos utilizando (por ejemplo C++) a código máquina que el ordenador puede ejecutar

    Además, en este proceso se crea un archivo ejecutable que puede utilizarse en otros ordenadores

    Hay lenguajes de programación que no utilizan un proceso de compilado sino un intérprete, que añade significado a las sentencias programadas

    Python es un lenguaje interpretado

  • Tipado dinámico

    El tipado de un lenguaje de programación es la forma que tiene el lenguaje de gestionar las variables y sus tipos

    Por lo general, en un lenguaje de programación se pueden crear variables de diferentes tipos (numero entero, número real, carácter, listas…)

    Existen lenguajes en los que antes de poder utilizar una variable tenemos que definirla y asignarle un tipo concreto de variable

    En un lenguaje no formal sería algo como: Quiero una variable entera que se llame Hermanos y Asigna a la variable casa el valor 3

    Sin embargo en Python no es necesario realizar esta asignación, dado que es un lenguaje con tipado dinámico y, en general, no es necesario definir una variable para poder utilizarla

    En Python escribiríamos directamente La variable hermanos tiene un valor de 2 y sería el interprete quien vería que queremos utilizar una variable que no existe, con el nombre Hermanos y asignarle el valor 2

  • Multi paradigma

    Existen varias formas de programar; con programación orientada a objetos como programación funcional

    Python admite ambos tipos

  • Multi plataforma

    Existen intérpretes de Python para múltiples plataformas, tales como MS Windows, Mac OS o Linux

Tanto el intérprete de Python como su extensa biblioteca estándar están a libre disposición en forma de código fuente y archivo ejecutable para las principales plataformas desde el sitio web de Python

Detrás de Python existe una amplia comunidad que provee amplio soporte a la vez que desarrolla y actualiza constantemente una miriada de librerías centralizado en un repositorio llamado Pypi

Ademas existe un código de buenas prácticas de programación en Python así como una guía de estilo, a fin de que el código sea de fácil lectura y comprensión

Instalación y modos de uso de Python

Python puede instalarse en nuestro ordenador desde la página de Python (recomendado instalar la última versión de Python)

Sin embargo, es más cómodo realizar la instalación mediante una distribución como Anaconda, que nos descarga el intérprete, diferentes editores y los paquetes y librerías más utilizados

Para instalar esta distribución tendríamos que:

  1. Ir a la página de descargas de Anaconda y descargar el paquete que se ajusta a tu sistema operativo y versión de Python deseada

    Se recomienda instalar la correspondiente a la versión más actual de Python

  2. Ejecutar el paquete y dejar que se instale completamente en el sistema
  3. Tras la instalación sería recomendable actualizar los paquetes

    Para ello abre un terminal y ejecuta:

    Mostrará una lista de librerías que van a a ser actualizadas

    Si estás conforme, acepta e iniciará la descarga y la instalación de las librerías

  4. En el caso de querer instalar una librería concreta, en el terminal ejecutaremos:

    Instalará la última versión de la librería presente en el repositorio de Anaconda que corresponda a nuestro sistema operativo y a nuestra versión de Python

En caso de que no esté presente en los repositorios, opcionalmente podemos instalar la librerías con pip

Para ello abre un terminal y ejecuta:

Para más información consultar la documentación de Anaconda

La forma más rápida de utilizar Python es mediante línea de comandos

Una vez tenemos un intérprete instalado en nuestro ordenador, abriríamos un terminal y escribiríamos

Esto abre una consola sobre la que podemos escribir y ejecutar código Python

Cada línea que ejecutásemos se realizaría la operación solicitada

Es una forma muy rápida para testear código pero es muy poco práctica, ya que perdemos la reutilización del código ejecutado

Es decir, si quisiésemos ejecutarlo de nuevo, tendríamos que escribir linea a linea el código otra vez

Lo más común es utilizar un editor como Spyder (se instala junto a Anaconda) o Jupyter notebooks, que es el que voy a utilizar yo para los ejemplos

Jupyter Notebook

Jupyter Notebook (anteriormente, IPython Notebook) es una aplicación web de código abierto que te permite crear y compartir documentos que contienen código en vivo, ecuaciones, visualizaciones y texto narrativo

Además, es una aplicación muy utilizada en el campo de la Ciencia de Datos (Data Science) para crear y compartir documentos que incluyen: limpieza y transformación de datos, simulación numérica, modelado estadístico, visualización de datos, aprendizaje automático y mucho más

Te permite editar y ejecutar documentos de notebook a través de cualquier navegador web de tu elección; y puede ejecutarse en un escritorio local que no requiere acceso a Internet o puede instalarse en un servidor remoto y acceder a través de Internet

También podemos ejecutar Jupyter Notebook sin ninguna instalación desde la página web del proyecto

O instalarlo usando pip, para ello abre un terminal y ejecuta:

Utilizar Jupyter Notebook

Una vez lo tengamos instalado, abriremos un terminal y ejecutaremos:

Esto imprimirá cierta información sobre el servidor de notebook en nuestro terminal, incluida la URL de la aplicación web (de forma predeterminada, http://localhost:8888)

En caso de necesitar conocer la ruta en la que tenemos instalado Jupyter, podemos ejecutar en el terminal:

En caso de necesitar conocer la versión de Python que tenemos instalado, podemos ejecutar en el terminal:

Antes de ejecutar este comando de una línea, se recomienda que verifiquemos que nuestro directorio de Anaconda PATH se haya agregado correctamente a las Variables de entorno, y si no es así, debemos ubicar nuestra ruta de directorio / archivo de Anaconda y agregarlo a las Variables de entorno, o si no podrías obtener un error:

jupyter no se reconoce como un comando interno o externo

Si el lanzamiento ha funcionado correctamente, se abrirá nuestro navegador por defecto con el Notebook abierto en el puerto 8888

Una vez ya dentro del Notebook, observaremos un botón llamado New que, al hacer clic, nos permite seleccionar un kernel de Python (las opciones dependen de lo que esté instalado en nuestro servidor local) del menú desplegable

Se recomienda seleccionar la última versión de Python que nos aparezca en el desplegable, a no ser que sea necesario usar otra por motivos de compatibilidad

Una vez seleccionado y aceptado, se nos abrirá nuestro primer cuaderno para escribir nuestros programas en Python

Nuestro primer script Python

Vamos a escribir un pequeño script de ejemplo para mostrar lo sencillo que es ejecutar un script Python desde Jupyter

Python tiene definido un sistema de almacenamiento y gestión de variables

En las variables podemos guardar información que queramos utilizar más adelante en el código con un nombre que nos sea fácil de recordar

En nuestro ejemplo, queremos calcular el área y el volumen de una esfera

Las ecuaciones que necesitamos son:

  • A = 4\pi \cdot r^2 para el área
  • V = \frac{4}{3}\pi \cdot r^3 para el volumen

En concreto, queremos calcular estos valores para una esfera de 7 de radio

Vamos a definir tres variables:

  • r

    Contiene el valor del radio, en este caso 7

  • A

    Contiene el valor del área calculada

  • V

    Contiene el valor del volumen calculado

¿Qué ventajas obtenemos definiendo variables?

Si cambiamos de idea y en vez de un radio 7 queremos que sea 6, tendríamos que cambiar el número de dos sitios (que en el caso de un programa en producción podrían ser cientos)

O queremos que sea el usuario quien indique el valor, de forma que no podemos saberlo a priori

Para escribirlo en Python usaremos una celda del cuaderno a la que le asignaremos en el menú desplegable (lo veremos a la izquierda del icono que parece un teclado) el valor Code

Una vez dentro de la celda, la usaremos como un editor de texto escribiendo:

Para ejecutarlo podemos usar el botón run o dejando pulsada la tecla de mayúsculas y después pulsando enter

El resultado de las variables permanecerá en memoria, pero nosotros no podremos verlo

Ahora vamos a usar la función print (similar a la función de C++) para poder ver el resultado de nuestro script Python

Para poder usar la constante Pi, se ha importado el módulo math con la sentencia import math y luego la hemos usado con math.pi

Como puede verse, para definir una variable escribimos el nombre con el que queremos bautizarla, el signo = y la expresión que queremos que sea asignada a esa variable

Comentarios en Python

Python soporta comentarios tipo Shell de Unix

Operadores en Python

Operadores en Python

Python posee operadores aritméticos, relacionales, lógicos, bit a bit, de asignación, pertenencia e identidad

Operadores aritméticos

Python suministra las operaciones básicas con los únicos añadidos de los operadores resto, cociente entero y exponenciación

Operadores aritméticos
Operador Operación Descripción
+ Suma Suma dos operandos
Resta

Resta al operando de la izquierda el valor del operando de la derecha

Utilizado sobre un único operando, le cambia el signo

* Multiplicación Multiplicación de dos operandos
/ División Divide el operando de la izquierda por el de la derecha (el resultado siempre es un float)
% Resto Obtiene el resto de dividir el operando de la izquierda por el de la derecha
// Cociente entero Obtiene el cociente entero de dividir el operando de la izquierda por el de la derecha
** Exponenciación El resultado es el operando de la izquierda elevado a la potencia del operando de la derecha

Nota En Python, usar el operador + aplicado a strings, concatena ambas strings en una sola

Operadores relacionales

Se emplean típicamente en las expresiones condicionales

Los operadores relacionales devuelven valores booleanos

Los operandos pueden ser tanto numéricos como strings

Operadores relacionales
Operador Operación Descripción
> Mayor que

True si el operando de la izquierda es estrictamente mayor que el de la derecha

False en caso contrario

< Menor que

True si el operando de la izquierda es estrictamente menor que el de la derecha

False en caso contrario

> = Mayor o igual que

True si el operando de la izquierda es mayor o igual que el de la derecha

False en caso contrario

< = Menor o igual que

True si el operando de la izquierda es menor o igual que el de la derecha

False en caso contrario

! = Distinto que

True si los operandos son distintos

False en caso contrario

= = Igual que

True si el operando de la izquierda es igual que el de la derecha

False en caso contrario

Los objetos de diferentes tipos, excepto los tipos numéricos, nunca se comparan igual

El operador == siempre está definido, pero para algunos tipos de objetos (por ejemplo, objetos de clase) es equivalente a is

Las instancias no idénticas de una clase normalmente se comparan como no iguales a menos que la clase defina el método __eq__()

Las instancias de una clase no se pueden ordenar con respecto a otras instancias de la misma clase u otros tipos de objeto, a menos que la clase defina los métodos __lt__() y __gt__()

Operadores lógicos

Los operandos lógicos está relacionados con los relacionales ya que normalmente los operandos que usan son resultado de expresiones relacionales

Los valores resultantes son booleanos

Operadores lógicos
Operador Descripción
AND True si ambos son True
OR True si uno de ellos es True
NOT Si era True pasa a False y viceversa

Operadores bit a bit

La forma de trabajar de estos operadores es convertir a binario los operandos y luego operar con ellos bit a bit

Operadores bit a bit
Operador Operación Descripción
& AND Cambia el bit a 1 si ambos eran 1
| OR Cambia el bit a 1 si uno de ellos era 1
^ XOR Cambia el bit a 1 si uno de ellos era 1, pero no ambos
~ NOT

Cambia el bit a 1 si era 0

Cambia el bit a 0 si era 1

<< Propagación a la izquierda Desplaza el valor hacia la izquierda introduciendo ceros, si se sale de rango se pierden valores
>> Propagación a la derecha Desplaza el valor hacia la derecha introduciendo por la izquierda el bit de signo y eliminando los valores que se salgan por la derecha

Nota Los operadores de propagación toman dos operandos: el primero es la variable a propagar y el segundo es el número de posiciones a propagar

Operadores de asignación

La asignación también es un operador que devuelve la variable modificada

El operador de asignación en Python es =

Los operadores de asignación que se muestran a continuación no son sino abreviaturas que hacen más cómoda y simples las expresiones, aunque a veces sean más ilegibles

Operadores de asignación
Operador Expresión Equivalencia
=

A = B = C

D = ‘Texto’

A = C

B = C

D = ‘Texto’

+ = A + = 4 A = A + 4
– = A – = 3 * B A = A – (3 * B)
* = A * = 2 A = A * 2
** = A ** = 2 A = A ** 2
/ = A / = 35 + B A = A / (35 + B)
% = A % = B A = A % B
// = A // = B A = A // B
>> = A >> = 1 A = A >> 1
<< = A << = B A = A << B
& = A & = (C + = 3) C = C +3
A = A & C
^ = A ^ = 2 A = A ^ 2
| = A | = C A = A | C

Operadores de pertenencia

Los operadores de pertenencia se utilizan para comprobar si un valor o variable se encuentran en una secuencia (list, tuple, dict, set o str)

Operadores de pertenencia
Operador Expresión Equivalencia
in Incluido

Devuelve True si el valor se encuentra en una secuencia

False en caso contrario

not in No incluido

Devuelve True si el valor no se encuentra en una secuencia

False en caso contrario

Operadores de identidad

Estos operadores se van a utilizar para comprobar si dos variables son, o no, el mismo objeto

Operadores de pertenencia
Operador Expresión Equivalencia
is Objetos iguales

Devuelve True si ambos operandos hacen referencia al mismo objeto

False en caso contrario

is not Objetos distintos

Devuelve True si ambos operandos no hacen referencia al mismo objeto

False en caso contrario

Preferencia

La preferencia de los operadores va a determinar el orden en que se ejecuten en una expresión determinada

Usando paréntesis controlaremos que las operaciones se lleven a cabo según nosotros queramos

En Python la preferencia de los operadores de mayor a menor es la siguiente:

Preferencia
Paréntesis ( )
Exponenciación **
Unarios ~ +
Mul / Div / Resto / Div ent * / % //
Suma / Resta +
Propagación << >>
Bit a bit &
Bit a bit ^ |
Relacionales < < = > > =
Igualdad = = !=
Asignación = + = – = * = * * = / = // = % = & = | = ^ = >> = << =
Identidad is is not
Pertenencia in not in

Tipos de datos en Python

Tipos de datos en Python

Los tipos en Python son una representación de los datos, ya que no requiere que las variables declaren su tipo, porque todos los tipos se convierten de forma automática

Podemos utilizar la función type para conocer el tipo de una variable

Tipo booleano

El tipo booleano simplemente distingue entre dos estados, un estado de éxito o de activado, valor verdadero, True, y un estado de fracaso o de desactivado, valor falso, False

Ambos estados no son susceptibles a mayúsculas y minúsculas

Normalmente, el resultado de operadores que devuelven un valor de tipo booleano son pasados a las estructura de control

Tipos numéricos

En Python existen 4 tipos diferentes de variables numéricas:

  • int

    número entero con precisión fija

  • long

    número entero en caso de sobrepasar el tamaño de un int

  • float

    número en coma flotante de doble precisión

  • complex

    número complejo (parte real + j parte imaginaria)

Es el intérprete el que decide el tipo de variable según lo que definimos

Sin embargo, podemos querer forzarlo nosotros mediante un cast

Tipo string

Un string es una cadena de caracteres delimitadas por comillas

Un literal de tipo string se puede especificar de dos formas diferentes:

  • entrecomillado simple

    se especifican entre los carácteres de apertura y de cierre

    Para especificar una comilla simple literal, se ha de escapar con una barra invertida (\). Para especificar una barra invertida literal, se duplica (\\)

    Todas las demás instancias de barras invertidas serán tratadas como una barra invertida literal: esto significa que otras secuencias de escape que podrían utilizarse, tales como \r o \n, serán mostradas literalmente tal y como se especifican, en lugar de tener cualquier otro significado especial

  • entrecomillado doble

    se especifican entre los carácteres " de apertura y " de cierre

    Python interpretará las siguientes secuencias de escape como caracteres especiales:

    Código Descripción
    \n avance de línea (LF o 0x0A (10) en ASCII)
    \r retorno de carro (CR o 0x0D (13) en ASCII)
    \t tabulador horizontal (HT o 0x09 (9) en ASCII)
    \\ barra invertida
    \" comillas dobles

Métodos

Vamos a ver algunos de los métodos para cadenas de uso más cotidiano

Métodos de formato

Afectan al formato del texto contenido en la cadena

  • capitalize()

    Realiza una copia de la cadena con la primera letra en mayúsculas

  • lower()

    Devuelve una copia de la cadena en minúsculas

  • upper()

    Devuelve una copia de la cadena en mayúsculas

  • swapcase()

    Devuelve una copia de la cadena en la que las mayúsculas son minúsculas y viceversa

  • title()

    Devuelve una copia de la cadena en la que la primera letra de cada palabra es mayúscula y las demás minúsculas

  • center(longitud, ‘caracter de relleno’)

    Devuelve una copia de la cadena de entrada centrada en la longitud indicada y entre el caracter de relleno que se indique

  • ljust(longitud, ‘caracter de relleno’): Devuelve una copia de la cadena alineada a la izquierda

  • rjust(longitud, ‘caracter de relleno’)

    Devuelve una copia de la cadena alineada a la izda/derecha

  • zfill(longitud)

    Devuelve una copia de la cadena rellenada con ceros a la izda hasta alcanzar la longitud indicada

Métodos de búsqueda

Nos permiten buscar dentro de una determinada cadena para comprobar si una subcadena está contenida en la cadena o cuántas veces aparece una subcadena

  • count(‘subcadena’)

    Devuelve un entero representando la cantidad de apariciones de la subcadena dentro de la cadena

  • find(‘subcadena’)

    Devuelve un entero representando la posición en la que empieza la subcadena dentro de la cadena

Métodos de validación

Nos devuelven siempre un booleano, es decir, True o False

Sirven para comprobar si una cadena cumple una determinada condición

  • startswith(‘subcadena’)

    Nos dice si una cadena comienza por una cadena determinada

  • endswith(‘subcadena’)

    Nos dice si una cadena termina por una cadena determinada

  • isalnum()

    Nos dice si una subcadena es alfanumérica

  • isalpha()

    Nos dice si una cadena es alfabética

  • isdigit()

    Nos dice si una cadena es numértica

  • islower()

    Nos dice si una cadena contiene sólo minúsculas

  • isupper()

    Nos dice si una cadena contiene sólo mayúsculas

  • isspace()

    Nos dice si una cadena contiene sólo espacios en blanco

  • istitle()

    Nos dice si una cadena tiene formato de título

Métodos de sustitución

Nos permiten sustituir texto para unificar el formato de diferentes cadenas que nos han llegado por vías diferentes

  • format(argumentos)

    Podemos tener una cadena que esté preparada para recibir una cierta cantidad de argumentos

    Al llamar a esta función e introducir los argumentos necesarios nos devuelve una copia de la cadena ya formateada

  • replace(‘subcadena buscada’, ‘subcadena que poner’)

    Busca dentro de la cadena una subcadena y la sustituye por la cadena indicada

  • strip()

    Elimina el carácter que indiquemos de la derecha y de la izquierda de la cadena

    Por defecto busca el carácter espacio

  • lstrip()

    Elimina los caracteres a la izquierda de una cadena

    Si introducimos como argumento una subcadena, busca esa subcadena y las combinaciones de los caracteres de la subcadena desde la izquierda hasta que no encuentre ninguno más

  • rstrip()

    Elimina los caracteres a la a derecha de una cadena

    Si introducimos como argumento una subcadena, busca esa subcadena y las combinaciones de los caracteres de la subcadena desde la derecha hasta que no encuentre ninguno más

Métodos de unión y división

Nos permiten sustituir texto para unificar diferentes cadenas que nos han llegado por vías diferentes o para separar una cadena en varias

  • join(iterable)

    Recibe como argumento un iterable y devuelve como resultado los elementos del iterable unidos por el carácter de la cadena sobre la que se aplica

  • partition(‘separador’)

    Devuelve una tupla de 3 elementos

    El primer elemento es lo que hay a la izquierda del ‘separador’, el segundo elemento es el separador y el tercer elemento es lo que hay a la derecha del separador

  • split(‘separador’)

    Devuelve una lista con todos los elementos encontrados al dividir la cadena por el separador

  • splitlines()

    Devuelve una lista donde cada elemento es una fracción de la cadena dividida en líneas

Listas

Se definen poniendo el contenido de la lista entre corchetes, separando cada uno de los elementos mediante una coma

Cada posición de la lista puede contener elementos de distinto tipo

Las listas son mutables, es decir, sus elementos pueden ser modificados

En Python los elementos de una lista se numeran desde 0 hasta longitud – 1

Para acceder al elemento de una lista se pone el nombre de la lista y a continuación el índice al que queremos acceder (si ponemos el índice con signo negativo empezará por el final de la lista)

Para acceder a un rango dentro de una lista tenemos diferentes opciones:

  • Tomar elementos desde el inicio: lista[:a]
  • Tomar elementos desde la posición a (incluida) hasta el final: lista[a:]
  • Tomar elementos desde a hasta b (sin incluir b): lista[a:b]

El inicio del rango siempre incluye el elemento en esa posición, pero no el final del rango

Métodos

Vamos a ver algunos de los métodos para listas de uso más cotidiano

  • append()

    añade un elemento al final de la lista

  • insert()

    se usa para insertar un elemento en el índice asignado

  • pop()

    elimina y devuelve el valor en la posición del índice asignado

  • reverse()

    reordena la lista de forma reversa

  • sort()

    reordena la lista de forma ascendente

Podemos encontrar todos los métodos en su documentación

Tuplas

Las tuplas son similares a las listas, se definen con paréntesis en vez de corchetes

Tienen la peculiaridad de ser inmutables

Como puede verse, se accede a los elementos de igual forma que con las listas

Diccionarios

Los diccionarios definen una relación de uno a uno entre clave valor entre {} y son mutables

Se definen colocando una lista separada por comas de pares clave:valor

Una vez definido, podemos acceder al valor asociado a una clave buscando por clave

Además, podemos buscar si una determinada clave existe o no en nuestro diccionario

Los diccionarios son un tipo de colección en Python que, a diferencia de las listas, están indexadas por palabras y no por números

Los valores pueden ser de cualquier tipo

Podemos pensar en un diccionario como un conjunto no ordenado de pares clave:valor con el requisito de que las claves tienen que ser únicas

Métodos

Vamos a ver algunos de los métodos para diccionarios de uso más cotidiano

  • keys()

    nos devuelve un iterable que contiene las claves del diccionario

  • values()

    nos devuelve un iterable que contiene los valores del diccionario

  • items()

    nos devuelve un iterable con los pares clave:valor del diccionario

Objetos

Los objetos se componen de un conjunto de valores, propiedades y un conjunto de métodos aplicados a esos valores

Son estos métodos los que nos permiten modificar el estado de dicho objeto, es decir, el valor de sus propiedades

En Python los objetos tiene las siguientes particularidades:

  • Todo es un objeto, incluyendo los tipos y clases
  • Permite herencia múltiple
  • No existen métodos ni atributos privados
  • Los atributos pueden ser modificados directamente
  • Permite monkey patching

    Las clases se crean en tiempo de ejecución pero pueden modificarse tras ser creadas

  • Permite duck typing

    El estilo de tipificación dinámica de datos en que el conjunto actual de métodos y propiedades determina la validez semántica, en vez de que lo hagan la herencia de una clase

  • Permite la sobrecarga de operadores
  • Permite la creación de nuevos tipos de datos

Vamos a crear un objeto Universitario de ejemplo en el que tendremos las propiedades: Nombre, Apellido, Edad y Dni

La instancia del objeto la crearemos mediante la asignación a una variable de la siguiente forma:

Es posible consultar la clase de un objeto con la función type, pero también se puede consultar a través de su atributo especial __class__

A su vez las clases tienen un atributo especial __name__ que nos devuelve su nombre en forma de cadena sin adornos:

Es posible inicializar todas las variables para que tengan el tipo que queremos, para ello usaremos el constructor de la clase pasándole los argumentos que necesitemos

El constructor es un método que se llama automáticamente al crear un objeto y se define con el nombre __init__ y mediante la palabra reservada self podremos referenciar al objeto actual

Si existe un constructor también debe existir un destructor que se llame al eliminar el objeto para que encargue de las tareas de limpieza como vaciar la memoria

Todos los objetos se borran automáticamente de la memoria al finalizar el programa, aunque para eliminarlos automáticamente disponemos del método especial __del__

No es aconsejable sobreescribir este método porque se maneja automáticamente, pero es interesante saber que es posible

El método __str__ es el que devuelve la representación de un objeto en forma de cadena

Se llama automáticamente es cuando imprimirmos una variable por pantalla con la función print

Por defecto los objetos imprimen su clase y una dirección de memoria, pero podemos cambiarlo sobreescribiendo su comportamiento

Por ejemplo, para el objeto universitario sobreescribimos el método __str__ para que nos muestre el nombre, edad, apellidos y dni

Un método especial interesante es el que devuelve la longitud

Normalmente está ligado a colecciones, pero nada impide definirlo en una clase y no redefinirlo, porque por defecto no existe en los objetos aunque sea el que se ejecuta al pasarlos a la función __len__

En nuestro ejemplo del objeto universitario no tiene mucho sentido contar su longitud, pero lo incluyo para mostrar su uso

Las propiedades de un objeto pueden describirse mediante otros objetos o incluso colecciones de ellos

La definición de métodos asociados a un objeto puede precisarse en la definición del objeto

Por ejemplo, para el objeto universitario sobreescribimos el método __str__ para que muestre el nombre, edad, apellidos, dni y notas medias del alumno

Para acceder a las propiedades o a los métodos usaremos el operador .

Herencia

La herencia es la capacidad que tiene una clase de heredar los atributos y métodos de otra, algo que nos permite reutilizar código

Para ver su utilidad, vamos a desarrollar un ejemplo modificando el objeto universidad

Partiremos de una clase base que hará de padre (superclase) de otras hijas que heredaran de ella (subclases)

Partimos de una clase Persona que contendrá todos los atributos comunes y dos clases hijas Alumno y Profesor que heredan de ella y tienen atributos o métodos propios

Para heredar los atributos y métodos de una clase en otra sólo tenemos que pasarla entre paréntesis durante su definición

Es posible utilizar el comportamiento de una superclase sin definir nada en la subclase, aunque en nuestro ejemplo si que les daremos comportamiento

Gracias a la flexibilidad de Python podemos manejar objetos de distintas clases masivamente de una forma muy simple

Vamos a empezar creando una lista con nuestros dos Personas de subclases distintas

Uno de los pocos fallos de Python es que si modificamos los valores dentro de un objeto, estos cambios se verán reflejados fuera del mismo

De la misma forma que en las colecciones, los objetos se pasan a las funciones por referencia

Afecta a la hora de hacer copias, creándose en su lugar un acceso al objeto en lugar de uno nuevo con sus valores anteriores

Para realizar una copia a partir de sus valores podemos utilizar la función copy del módulo con el mismo nombre

La función copy se puede utilizar también para copiar colecciones

La herencia múltiple permite a una subclase heredar de múltiples superclases

Esto conlleva un problema, y es que si varias superclases tienen los mismos atributos o métodos, la subclase sólo podrá heredar de una de ellas

Además de superclases también puede heredar de forma múltiple de subclases

En estos casos Python dará prioridad a las clases más a la izquierda en el momento de la declaración de la subclase

Hemos usado la palabra reservada pass para indicarle al intérprete que ignore el código del cuerpo y utilice el de la clase object o el de las superclases (subclases) que le indiquemos

Valor nulo

En Python a las variables se les puede asignar un valor que indica el valor vacío, este valor es el valor NULL

Una variable es considerada None si:

  • se le ha asignado la constante None
  • no se le ha pasado un atributo a una función

La constante None es insensible a mayúsculas o minúsculas

Estructuras de control en Python

Estructuras de control en Python

Para las estructuras de control, Python posee las sentencias de control típicas de los lenguajes de alto nivel

Declaración de variables

A las variables en Python no se les asigna un tipo predefinido

En Python el tipo de las variables depende del valor que contengan las mismas en cada momento

Por tanto se realiza una conversión automática de tipos

Python reconoce los siguientes tipos de valores:

  • Números
  • Valores booleanos

    true y false

  • Strings
  • Listas

    tipo de datos que asocia valores con un valor numérico poniendo el contenido de la lista entre corchetes, separando cada uno de los elementos mediante una coma

  • Tuplas

    tipo de datos que asocia valores con un valor numérico poniendo el contenido de la lista entre paréntesis, separando cada uno de los elementos mediante una coma

    Tienen la peculiaridad de ser inmutables

  • Diccionarios

    tipo de datos que asocia un valor a una clave y permite buscar por esas claves

  • Objetos

    Creados por el programador o predefinidos por el lenguaje

Al tratarse de un lenguaje con tipado dinámico, no es necesario decirle al intérprete su tipo de variable, sino que es él quien decide el tipo que asignará

Los nombres con los que podemos nombrar variables en Python siguen varias normas:

  • Los nombres empiezan siempre por letra
  • Dentro del nombre se puede usar cualquier letra o número dentro de la codificación utf-8
  • No se pueden usar las palabras reservadas de Python, que son usadas en su sintaxis de su código

    Para más información sobre estos nombres consultar en la documentación de Python

Notar que Python distingue entre mayúsculas y minúsculas, por lo que dos variables con el mismo nombre pero que contenga una letra en mayúscula serán entendidas como dos variables distintas

Por ejemplo Variable es diferente de variable

Además existen ciertos convenios para reducir errores y mejorar la legibilidad

Para evitar errores se recomienda no usar ciertos caracteres:

  • No usar ni l (ele minúscula) ni I (i mayuscula), ya que dependiendo del tipo de fuente que se use pueden confundirse entre si o con el número 1
  • No usar O (o mayúscula) ya que dependiendo del tipo de fuente que se use puede llevar a confusión con el número 0

A fin de favorecer la legibilidad se hacen las siguientes sugerencias:

  • nombredevarible es poco legible, por estar todo junto y puede dificultar su legibilidad
  • nombreDeVarible es un poco más legible, al aplicar letra mayúscula al iniciar palabra
  • nombre_de_variable es la que mejor legibilidad aporta, al aplicar guión bajo al iniciar palabra, que es la forma más recomendada

La sentencia if

La sentencia if tiene la forma:

En caso de que la condición sea verdadera se ejecutará la instrucción 1; en caso contrario se ejecuta si existe la instrucción 2

El uso de la sentencia else es opcional, por eso en la definición se han utilizado los corchetes

Si se omite, sólo se tendrá en cuenta el bloque de instrucciones cuando la condición sea verdadera

Un bloque de instrucciones es un conjunto de instrucciones tabulaciones después de la sentencia if, al saltar de línea después del símbolo :

Son obligatorias ya que le dicen al interprete que el bloque de instrucciones pertenece a la sentencia if

Si se omiten y la condición era verdadera, se ejecutará la siguiente instrucción

En caso contrario, se ejecutará la subsiguiente instrucción, de forma independiente al estado de la condición

De este modo, la omisión de las tabulaciones después de la sentencia if nos permitirá escribirlo todo en una única línea después del símbolo :

La sentencia elif

También podemos utilizar sentencias if anidades mediante la sentencia elif

Las sentencias elif funcionan igual que una sentencia if

Pero sólo se ejecutarán en caso de que la condición de la sentencia if fuese falsa

Podemos añadir opcionalmente una sentencia else al final pero sólo se ejecutará si todas las condiciones anteriores fuesen falsas

La sentencia while

La sentencia while tiene la forma

Si se cumple la condición, se ejecuta la instrucción o el bloque de instrucciones y se repite el proceso hasta que deje de cumplirse la condición

La sentencia for

Este bucle, tiene una sintaxis muy parecida al for-each de Java

Itera una variable var sobre todas las propiedades de un objeto obj que se le pasa

Así para cada valor de var se ejecutaran las sentencias del bucle

Por lo tanto, el bucle tendrá tantas iteraciones como propiedades el objeto y en cada iteración la variable tendrá el valor de la propiedad del objeto correspondiente con dicha iteración

Su sintaxis es:

Iteradores

En Python existen diferentes estructuras que son conjuntos de elementos, son las llamadas colecciones

Este tipo de estructuras son iterables, es decir, se pueden recorrer elemento por elemento

Algunos tipos de variable que son iterables son:

  • Cadena de caracteres (str)
  • Lista (list)
  • Tupla (tuple)
  • Diccionario (dict)

Su uso es útil para recorrer elementos de colección como las listas, las tuplas o los objetos

Además, muchas veces queremos repetir un bucle un número determinado de veces

Para esto puede ser útil la función range(n)

Esta función genera un iterable que va desde 0 hasta n – 1

La sentencia break

La sentencia break se puede colocar dentro de un bucle o bucles anidados

Cuando se ejecuta la sentencia break se abandona el bucle más interno

A todos los efectos la sentencia break actúa como un salto a la instrucción siguiente al bucle en el que se ejecuta

La sentencia continue

La sentencia continue, no abandona el bucle si no que hace que se ejecute la siguiente iteración

En el bucle while la ejecución del continue hace que el flujo del programa salte a la condición

En el bucle for la ejecución del continue hace que se ejecute la expresión de incremento, para después continuar normalmente con la condición

Es decir, la ejecución del continue evita que se ejecute el resto del cuerpo del bucle