Introducción á Linguaxe Java
De ASIRodeira
Índice |
Introducción á Programación Orientada a Obxecto.
Todas as linguaxes de programación pretenden acercar a programación das computadoras á linguaxe humana. A linguaxe ensamblador permite enfocar a programación como unha serie de movementos de información e de operacións matemáticas permitíndonos esquecernos do funcionamento electrónico da máquina; un paso máis son as linguaxes imperativas (C, COBOL, FORTRAN, Pascal ...), que permiten acercar aínda máis a programación á linguaxe humana. Sen embargo, a programación coas linguaxes imperativas aínda esixe que pensemos en termos da estructura dos computadores máis que en termos da estructura do problema a resolver.
A programación orientada a obxectos (OOP) pretende proporcionar utilidades ó programador para representar os distintos elementos que interveñen nun problema. Deste xeito, un programa sería un conxunto de obxectos interactuando entre eles.
Por exemplo, un programa que controlara automaticamente a luz dunha habitación podería constar dun obxecto lámpada e un obxecto sensor. O obxecto sensor enviaría ó obxecto lámpada a mensaxe on cando a luz baixa dun límite e off cando sube dese límite.
Os obxectos almacenan datos é se lles pode facer peticións para que realicen operacións sobre si mesmo. En teoría pódese coller calquera compoñente conceptual do problema (edificios, sofás, lámpadas, ... etc) e representalos mediante un obxecto no programa.
Existen distintas aproximacións á programación orientada a obxectos, en C++ por exemplo, pode escribirse código "de programa" sen formar parte de ningún obxecto, e en JAVA Todo é un obxecto.
- Un programa é un conxunto de obxectos dicíndolle uns ós outros qué facer mandándolles mensaxes. En realidade as mensaxes consistirán en chamar funcións pertencentes ó obxecto ó que lle mandamos a mensaxe.
- Cada obxecto ten a súa propia memoria composta doutros obxectos. É dicir, pódense crear novos obxectos xuntando varios obxectos xa existentes, aumentando a complexidade do programa ó mesmo tempo que permanece oculta nos novos obxectos.
- Cada obxecto ten un tipo ou 'clase'. Ou sexa, cada obxecto é un elemento pertencente a unha clase determinada. A característica máis importante dunha clase son as mensaxes que pode recibir (as súas funcións).
As mensaxes as que pode responder un obxecto atópanse especificadas na súa interface, que ven imposta polo tipo de obxecto (a súa clase). De aquí en diante especificaremos os obxectos do seguinte xeito:
Por exemplo, en JAVA un obxecto deste tipo utilizaríase do seguinte xeito:
Lampada Luz = new Lampada(); Luz.On();
Neste caso, Lámpada é o nome da clase (o tipo) e Luz é un 'recipiente' onde se pode almacenar unha referencia a un obxecto concreto dese tipo. Mediante o operador new créase un novo obxecto do tipo Lámpada e almacénase a referencia en Luz. A partir dese instante pódense enviar mensaxes o novo obxecto utilizando Luz. Por exemplo 'On' prende a lámpada.
As mensaxes que se lle poden enviar a un obxecto reciben o nome de métodos da clase (en realidade son funcións que operan sobre a propia clase). Ademais, un obxecto pode estar composto por outros obxectos que reciben o nome de membros da clase ou atributos.
Entre os programadores nas linguaxes orientadas a obxecto pódese distinguir entre os creadores de clases e os programadores cliente, de xeito parecido ós creadores de librerías e os usuarios das mesmas nas linguaxes tradicionais. Os creadores de clases encárganse de desenvolver novas clases de obxectos de utilidade xeral, mestras que os programadores cliente utilizarán estas para desenvolver o seu proxecto particular.
Unha das maiores vantaxes da programación orientada a obxectos é que permite a reutilización de código con gran facilidade ademais de simplificar tamén o mantemento do mesmo. Como as aplicacións non son máis que un conxunto de obxectos enviándose mensaxes, é posible modificar os obxectos de forma independente sen afectar ó resto do programa. Tamén é posible verificar o comportamento dos obxectos de forma individual, e localizar o obxecto erróneo con facilidade.
Para conseguir todo esto é necesario facer que o usuario da clase non poda modificar partes críticas do obxecto que poidan afectar o correcto funcionamento do mesmo, e dicir, separar o interface da clase da súa implantación, ocultando o cliente o código de funcionamento interno da clase (Unicamente nos interesa que unha lámpada se pode prender, non cómo). Para facer esto as linguaxes orientadas a obxecto posúen unha serie de modificadores de acceso, que en JAVA son public, private e protected, ademais do acceso por defecto que se chama friendly.
- public : Todas as outras clases teñen acceso os métodos ou membros public.
- private : Unicamente se pode acceder os métodos ou membros private desde dentro da propia clase.
- protected : Unicamente se pode acceder os métodos ou membros protected desde dentro da propia clase ou desde unha clase herdada.
- friendly : Unicamente se pode acceder os métodos ou membros friendly desde as clases desenvolvidas no mesmo package.
Unha vez que temos unha clase creada e probada, debería ser unha unidade de código útil e reutilizable. A forma máis simple de reutilizar unha clase e creando obxectos desa clase, pero tamén é posible colocar un obxecto desa clase dentro dunha clase nova. Unha clase pode estar composta de calquera número de outros obxectos.
Esta forma de crear novas clases chámase composición ou agregación, e a miúdo se indica como unha relación 'ten un', por exemplo, un coche ten un motor, e dicir que o obxecto coche ten un membro que é un obxecto motor.
A Herdanza
Outro concepto importante na programación orientada a obxectos é a herdanza. Moitas veces precisamos dunha nova clase que se comporte de xeito moi similar a unha xa existente, co que sería bo ter un xeito de facer unha copia da clase orixinal e logo facer modificacións e engadidos á copia para ter a nova clase. Esto conséguese mediante a herdanza. A clase orixinal recibe o nome de clase base, pai ou super, mentres que a nova clase chámase clase derivada ou clase filla.
Unha vez que derivamos unha clase herdaremos o seu interface e membros de clase, e poderemos reescribir os métodos que queiramos para modificar o seu comportamento. Os métodos que non reescribamos manterán o comportamento da clase base. Este tipo de relación chámase tamén 'é un'. Por exemplo: Un gorrión é un paxaro.
Tamén é posible engadirlle novos métodos ó interface da clase derivada ampliando así a súa funcionalidade. Neste caso a relación chámase 'é coma un'. Cando teñamos que engadir novos métodos a unha clase derivada paga a pena cuestionarse si eses novos métodos poden pertencer en realidade a clase base.
Vexamos un exemplo:
Tomemos a clase figura xeométrica 'Figura'. Cada 'Figura' ten uns atributos (obxectos membros): tamaño, cor, e posición e é posible realizar sobre ela varias operacións distintas: debuxala, borrala, movela, coloreala ou rotala. A partir de aquí podemos desenvolver novas clases coma 'Circulo', 'Cadrado' e 'Triangulo', que a súa vez son 'Figuras', e polo tanto poderán automaticamente debuxarse, moverse, borrarse, colorearse ou rotarse, ademais de ter un tamaño unha cor e unha posición. Cando creemos un novo obxecto de tipo 'Círculo' poderemos tratalo como un 'Círculo' ou como unha 'Figura', xa que os 'Círculos' son 'Figuras'. O tratar un obxecto dunha clase derivada coma si fora da clase base chámase upcasting.
Se a forma de debuxarse, moverse, borrarse, colorearse ou rotarse non coincide coa forma en que o fan as 'Figuras' en xeral, será necesario reescribir o método na clase derivada (sobreposición de métodos). Tamén é posible escribir varias versións dun mesmo método dentro dunha clase, o que recibe o nome de sobrecarga de métodos sempre e cando os argumentos que reciban os métodos sexan distintos, xa que o intérprete utilizará os argumentos para elixir o método a chamar.
As veces non é posible implantar todos os métodos dunha clase por ser demasiado xeral, servindo esta unicamente como base para derivar novas clases. Unha clase que non ten implantada toda a súa interface recibe o nome de clase abstracta, e o método que non está programado chámase método abstracto. Tamén é posible definir clases que non teñan definido ningún dos métodos do seu interface, vendo a ser un 'molde' que teñen que cumprir todas as clases derivadas. Estas clases denomínanse interfaces.
No caso particular de JAVA, todas as clases que se poden utilizar derivan dunha clase xenérica chamada Object, o que lles da a tódolos obxectos algunhas características comúns. JAVA é polo tanto unha linguaxe con unha xerarquía de clases con unha soa raíz, a diferencia de outras linguaxes de raíz múltiple como C++. Co JDK de JAVA proporciónase unha extensa librería de clases que cobren a maior parte das necesidades básicas de calquera programador.
| NOTA |
| En algunhas linguaxes unha clase pode derivar de varias clases (herdanza múltiple), sen embargo unha clase en JAVA so pode ter unha clase base (herdanza sinxela). |
É moi importante coñecer a xerarquía de clases dun obxecto, xa que nos proporcionará a información necesaria sobre o seu uso e posibilidades.
Coma xa vimos antes é posible crear novas clases derivando a partir dunha clase base. Deste xeito podemos ter a clase 'Figura' e derivar dela a clase 'Triangulo' co que un 'Triangulo' é unha 'Figura'. A causa de esto é posible tratar a un obxecto da clase 'Triangulo' coma un 'Triangulo' ou como unha 'Figura' segundo nos conveña (ollo, o contrario non é certo xa que unha 'Figura' non ten por que ser un 'Triangulo'). Tendo en conta esto... ¿ Qué sucedería no caso seguinte ?
Coma os pingüíns teñen un xeito de moverse distinto a maior parte dos paxaros é necesario reescribir o método de moverse, o que non é o caso da pomba. O programa principal invoca ó método move () da clase PAXARO, e tanto os obxectos PINGÜÍN como POMBA son tamén obxectos PAXARO. ¿ A qué método move () se chamará ?. Dende o punto de vista do programador é irrelevante o método que se invoque, xa que o único que lle interesa é que o paxaro se mova, non cómo. Por outra banda o compilador non pode sabelo en tempo de compilación, xa que depende do obxecto PAXARO de que se trate. A solución que toman as linguaxes orientadas a obxecto consiste en non determinar a dirección do método o que se vai a saltar hasta que se execute o programa, sendo en tempo de execución cando se decide a función a chamar. Podes ver mellor o funcionamento executando o seguinte exemplo:
public class ClaseBase { void Nome () { System.out.println("Son a clase BASE"); } /* Método Inicial do Programa */ public static void main (String[] args) { ClaseBase n,n1,n2,n3; n= new ClaseBase(); n.Nome(); n1=new ClaseDerivada1(); n1.Nome(); n2=new ClaseDerivada2(); n2.Nome(); n3=new ClaseDerivada3(); n3.Nome(); } } class ClaseDerivada1 extends ClaseBase { void Nome () { System.out.println("Son a Clase Derivada1"); } } class ClaseDerivada2 extends ClaseBase { void Nome () { System.out.println("Son a Clase Derivada2"); } } class ClaseDerivada3 extends ClaseBase { }
Constructores e Destructores
Como podemos observar no exemplo anterior, para crear un obxecto en JAVA é necesario utilizar unha instrucción especial chamada new. En JAVA todas as declaracións son referencias a obxectos (é dicir, unha variable que almacenará a dirección de memoria onde se atopa o obxecto referenciado) e é necesario crear os obxectos mediante chamadas explícitas a new mentres que outras linguaxes coma C++ si que permiten crear obxectos mediante unha simple declaración.
Sen embargo o que tódalas linguaxes orientadas a obxecto teñen en común é a existencia dos métodos constructores. Un método constructor invócase de xeito automático no instante en que se crea un novo obxecto. Os métodos constructores en case todas as linguaxes orientadas a obxectos teñen o mesmo nome que a clase.
Se nós o escribir a clase non proporcionamos un método constructor o sistema creará ún por defecto.
Por suposto, é posible sobrecargar o método constructor de xeito que teñamos varias formas de crear un obxecto. Cando nós creamos un novo obxecto, invocaranse de xeito automático os métodos constructores de toda a xerarquía de clases asociada comezando pola raíz, xa que un obxecto derivado é ademais un obxecto da clase pai.... etc.
A maior parte das linguaxes orientadas a obxecto teñen tamén outro método especial destinado a liberar toda a memoria utilizada por un obxecto cando é destruído. Este método que se invoca de xeito automático o destruír un obxecto chámase destructor. JAVA, sen embargo non ten métodos destructores, xa que a memoria é liberada de xeito automático cando é necesario mediante un recolector de lixo. O recolector de lixo encárgase de eliminar os obxectos que non están a ser utilizados cando o sistema ande baixo de memoria. Esto, aínda que é unha gran vantaxe para o programador (evita que nos poidamos esquecer de liberar a memoria), é tamén un problema, xa que non temos forma de saber o instante en que un obxecto é eliminado. Podes observar todo o proceso executando o seguinte exemplo:
public class ClaseBase { static int i; ClaseBase() { // Constructor i++; System.out.println("Creando Obxecto ClaseBase "+i); } void Nome () { System.out.println("Son a clase BASE"); } /* Método de Inicio */ public static void main (String[] args) { ClaseBase n,n1,n2,n3; n= new ClaseBase(); n.Nome(); n1=new ClaseDerivada1(); n1.Nome(); n2=new ClaseDerivada2(); n2.Nome(); } } class ClaseDerivada1 extends ClaseBase { ClaseDerivada1() { System.out.println("Creando Obxecto ClaseDerivada1 ..."); } void Nome () { System.out.println("Son a Clase Derivada1"); } } class ClaseDerivada2 extends ClaseBase { void Nome () { System.out.println("Son a Clase Derivada2"); } }
Técnicas de Desenvolvemento Orientado a Obxectos: UML (Unified Modeling Language)
UML permite modelar, construir e documentar os distintos elementos que forman un sistema software orientado a obxectos. Pode considerarse hoxe en día como o estándar da industria, e é a base de moitas ferramentas CASE como Rational Rose. UML facilita o modelado de sistemas como conceptos de negocio e funcións dun sistema, así como o deseño de bases de datos ou compoñentes de software.
Para todo esto UML define diversos diagramas, dos que destacaremos os seguintes:
Casos de Uso
Os casos de uso son un conxunto de escenarios que describen a interacción entre o usuario e o sistema a modelar. Os compoñentes principais dos casos de uso son os actores e os casos de uso. O actor e o elemento que desencadena unha secuencia de accións nun escenario determinado.
Un caso de uso é unha vista externa do sistema que representa algunha acción que o usuario debe iniciar para realizar algunha tarefa. O seguinte gráfico pode servir de exemplo:
O Diagrama de Casos de Uso pode ser moi útil para ter unha visión global do que é posible realizar nun sistema e dos elementos básicos co compoñen. Pero o que describe con detalle os casos de uso son os documentos de uso. Estos documentos describen as accións dos actores para realizar unha función.
Vexamos un exemplo:
| Nome: | Sacar diñeiro do caixeiro |
| Autor: | Xavi Taboada |
| Data: | 20/10/2000 |
| Descripción:
Reintegro dunha cantidade do caixeiro automático | |
| Actores:
Usuario do Caixeiro xa identificado, e que elexiu a opción de reintegro | |
| Precondicións:
O usuario ten que ser cliente dun banco da rede de caixeiros, terse identificado co seu PIN e ter elexido a opción de reintegro | |
| Fluxo Normal:
1.- Se solicita a cantidade. O usuario Escribe a cantidade e pulsa continuar. 2.- Se comproba o saldo 3.- Se solicita confirmación 4.- Se actualizan os datos da conta e se entrega o diñeiro e o recibo | |
| Fluxo Alternativo:
a) Si o usuario non confirma o reintegro ou ben pulsa cancelar en calquera momento se devolve a tarxeta. b) Si o usuario non ten saldo suficiente, se lle avisa e se expulsa a tarxeta. | |
| Poscondicións:
O usuario queda co saldo disminuido na cantidade reintegrada | |
Diagrama de Clases
O diagrama de clases é unha visión xeral das clases de obxectos que interveñen nun problema e a relación entre todos eles. Podemos distinguir entre os modelos conceptuais, que representan elementos do propio problema, e os modelos de clases, que representan elementos da solución software.
Clases
As clases represéntanse indicando o nome da clase e logo os atributos e os métodos separados por unha liña.
Composición e Agregación
A Agregación consiste en construír novas clases que conteñen obxectos como atributos. Estes obxectos que forman parte da nova clase poden ter ligada a súa existencia coa clase (Composición) ou existir independentemente da existencia ou non de obxectos da clase (Agregación).
Herdanza
A herdanza indica que unha clase ten todas as características da clase da que hereda. A todos os efectos un obxecto da clase herdada é tamén un obxecto da clase orixinal.
Asociación
A asociación indica unha relación entre dúas clases. Por exemplo entre a clase "Cliente" e a clase "Crédito" pode existir unha relación de tipo "Solicita un".
Instanciación (Uso)
Indica que unha clase crea un obxecto. Serve para indicar dependencia do obxecto creado respecto da clase que o crea, pero non é almacenado dentro dela (nese caso sería unha agregación).
Diagramas de Interacción
E unha descrición das interaccións entre os obxectos que compoñen a aplicación e os actores que poden intervir. Se indican polo tanto as distintas mensaxes enviadas e quen é o desencadenante das mesmas.
Novas Características de J2SE 5.0
- Engadir tipos parametrizados. JDK 1.5
- Engadir nova forma do for
- Conversión Automática de tipos primitivos
- enums
- static imports
- Metadatas
- Número variable de argumentos
- Salida por pantalla con formato (printf)
- Nueva clase Scanner para entrada con tipos
- java.util.concurrent
Introducción á Linguaxe Java
JAVA é unha linguaxe orientada a obxecto independente da plataforma; unha aplicación escrita en JAVA funcionará baixo calquera máquina e sistema operativo. O compilador JAVA xera un 'código ensamblador' que se interpreta por un intérprete JAVA (este tipo de códigos non asociados con ningún procesador físico chámanse byte-codes).
O intérprete JAVA implementa unha 'máquina virtual JAVA' que é unha máquina simulada por software que executa o 'código ensamblador JAVA'. Hoxe en día a maior parte dos navegadores WEB dispoñen dunha máquina virtual JAVA, moitas veces dispoñible como un módulo (plug-in) que pode ser actualizado por separado.
A independencia da plataforma que proporciona JAVA ten un prezo en velocidade nas aplicacións xa que é necesario interpretar as instruccións en byte-codes e traducilas en chamadas a instruccións nativas. Para minimizar esta perda de tempo moitas máquinas virtuais empregan JIT (Just In Time), que consiste en almacenar en memoria as instruccións traducidas o ensamblador nativo de xeito que unicamente se interpretarán a primeira vez que son levadas a cabo.
Como todas as linguaxes de programación, JAVA proporciona unha extensa biblioteca de clases de propósito xeral que facilita moito a programación de aplicacións. Estas clases atópanse agrupadas por 'tipo de utilidade' no que se chama 'paquetes' ou packages. Cando nós desenvolvemos unha nova clase podemos indicar o package do que vai formar parte, e se non o indicamos pasará a formar parte do 'package por defecto'. Para utilizar as clases pertencentes a un package en concreto será necesario importar o package mediante unha instrucción import.
JAVA é unha linguaxe sensitiva ás maiúsculas e minúsculas, de xeito que Variable é un identificador distinto de variable. Ademais é preciso seguir unha serie de regras estrictas na creación do código fonte:
- Cando desenvolvamos unha aplicación unicamente pode haber unha clase pública dentro de un mesmo ficheiro fonte.
- Os ficheiros fonte deberán chamarse exactamente igual que a clase pública que conteñen e con extensión .java.
- Cando compilemos un ficheiro fonte o compilador xerará un ficheiro compilado con extensión .class por cada clase definida.
- O intérprete de java invocarase co nome da clase publica como argumento para iniciar a aplicación.
Este xeito de traballar facilita a actualización de aplicacións sustituindo unicamente as clases modificadas e facendo innecesaria a recompilación do código que non cambiou.
Outra característica de JAVA é que todo é un obxecto que descende da clase raíz Object. Sen embargo, para manipular os obxectos o que utilizaremos serán referencias (é dicir, variables que almacenarán a dirección do obxecto), por exemplo:
String nome;
Definirá unha variable que poderá almacenar unha referencia a un obxecto da clase String. Se quixeramos enviar unha mensaxe ó obxecto nome produciríase un erro, xa que nome non está referenciando ningún obxecto. Para crear obxectos utilízase o operador new invocando un constructor da clase:
String nome=new String("Introducción");
Deste xeito creamos a variable para referenciar un obxecto da clase String, creamos o obxecto da clase String e o almacenamos na variable nome. Tamén é posible facelo do seguinte xeito:
String nome; nome = new String("Introducción");
É polo tanto importante ter en conta que estaremos sempre manexando referencias a obxectos.
Para acelerar a execución das aplicacións, JAVA proporciona unha serie de tipos de datos primitivos que non son clases, evitando así todo o proceso de creación das clases almacenando a información na pila do programa. Os datos primitivos non son referencias, se non que as variables almacenan o valor directamente. Sen embargo por cada tipo primitivo JAVA ten unha clase asociada que é equivalente:
|
Tipo Primitivo |
Valor por defecto |
Tamaño en Bytes |
Mínimo |
Máximo |
Clase asociada |
|
boolean |
false |
-- |
true, false |
true, false |
Boolean |
|
char |
'\u0000' (null) |
16 - bit |
Unicode 0 |
Unicode 216-1 |
Character |
|
byte |
0 |
8 - bit |
-128 |
+127 |
Byte |
|
short |
0 |
16 - bit |
-215 |
+215-1 |
Short |
|
int |
0 |
32 - bit |
-231 |
+231-1 |
Integer |
|
long |
0 |
64 - bit |
-263 |
+263-1 |
Long |
|
float |
0.0 |
32 - bit |
IEEE754 |
IEEE754 |
Float |
|
double |
0.0 |
64 - bit |
IEEE754 |
IEEE754 |
Double |
|
void |
-- |
-- |
-- |
-- |
Void |
A visibilidade dos datos primitivos e dos obxectos ven dada polos delimitadores { e }, sendo visible dentro dos delimitadores no que está definido.
Ademais destes tipos primitivos JAVA incorpora dúas clases para realizar aritmética de alta precisión (BigInteger e BigDecimal) cos que se poden realizar exactamente as mesmas operacións que cos tipos primitivos e que poden almacenar números de calquera tamaño sen perder información.
A diferencia de outras linguaxes orientadas a obxecto, en JAVA non se liberan de forma específica os obxectos creados, se non que é o sistema o que decide cando é necesario liberar a memoria ocupada polos obxectos que non están a ser utilizados mediante o chamado recolector de lixo.
Como se pode observar na táboa anterior, existen unha serie de valores por defecto que tomarán as variables de clase no caso de non ser inicializadas. Sen embargo, co obxecto de evitar erros, as variables dos métodos teñen que ser inicializadas antes de poder utilizalas.
A inicialización pode realizarse no mesmo instante da definición, ou ben cunha sentencia posterior. O seguinte exemplo mostra a orde de inicialización das variables de clase.
class Elemento { Elemento(int num) { System.out.println("Creando elemento "+num); } } class Proba { Elemento e1=new Elemento(1); Proba() { System.out.println("Creando obxecto Proba"); e3=new Elemento(33); } Elemento e2=new Elemento(2); void funcion() { System.out.println("Chamando a Proba.funcion()"); } Elemento e3=new Elemento(3); } public class OrdeInicio { public static void main(String[] args) { Proba p=new Proba(); p.funcion(); } }
Creación de Clases e de Métodos.
Para crear novas clases utilizaremos a palabra reservada class do seguinte xeito:
class NomedaClase { /* Definición da clase */ }
Cando definimos unha clase (e unha aplicación JAVA non é máis que definicións de clases, creación de obxectos desas clases e envío de mensaxes entre os obxectos) é posible poñer dentro dela dous tipos distintos de elementos: datos (atributos) e funcións (métodos).
Os métodos son funcións definidas do seguinte xeito:
TipodeDatoaVoltar NomedeFunción ( /* lista variables argumento separadas por comas */) { /* Corpo da función */ }
para voltar o dato de retorno da función utilízase return nomeVariable;, ou simplemente return si a función é void.
Os datos son definicións de variables da forma: tipodedato nomevariable;
Se os datos son tipos primitivos terán o valor por defecto indicado anteriormente, se non, serán referencias a un obxecto da clase tipodedato que aínda non referencian a ningún obxecto en concreto. Para inicializar os datos membros dunha clase utilízase normalmente un método constructor. Un método constructor é un método especial que se chama automaticamente ó crear un obxecto dunha clase, ten que chamarse exactamente igual que a clase e devolve un obxecto desa mesma clase (aínda que non se pon nada especificamente). Todas as clases teñen un método constructor, e se nós non o escribimos JAVA o crea por defecto.
Unha vez definida unha clase poderemos crear novos obxectos desa clase mediante o operador new do seguinte xeito:
NomedaClase n; n=new NomedaClase (/* Argumentos do constructor */);
Co que crearemos un novo obxecto cos seus propios datos e métodos.
Unha posibilidade tanto dos métodos como dos datos é ser de tipo static. Un dato ou método static recibe o nome de dato ou método de clase, xa que existe aínda que non se cree ningún obxecto desa clase, e ademais ese dato é común a tódolos obxectos desa mesma clase e ós seus descendentes.
Tamén é posible crear novas clases derivando dunha clase xa existente. Nese caso a nova clase terá tódalas características da clase da que deriva, ademais de poder engadirlle novos métodos, novos datos ou redefinir métodos xa existentes.
Para derivar unha clase de outra emprégase a palabra clave extends do seguinte xeito:
class NomedaClase extends NomedaClaseBase { /* Definición da clase */ }
En JAVA unha clase unicamente pode derivar de outra, e dicir é de herdanza simple. Vexamos un exemplo de programa Java:
// O primeiro programa JAVA import java.util.*; public class HolaMundo { public static void main (String[] args) { System.out.println("Hola Mundo. Hoxe e: "); System.out.println(new Date()); } }
Para probar o exemplo este código debe chamarse HolaMundo.java e ó compilalo con javac producirá o ficheiro HolaMundo.class. Para executalo invocaremos o intérprete JAVA do seguinte xeito: java HolaMundo. O intérprete executará entón o método main que atope dentro da clase principal do módulo, neste caso da clase HolaMundo. En Java os ficheiros teñen que chamarse igual que a clase pública que conteñen, e un ficheiro so pode ter unha clase pública.
Vexamos outro exemplo:
import java.util.*; class Mundo { Date creacion; String nomeMundo; long distanciaSol; long masa; Mundo () { this ("Descoñecido",0,0); } Mundo (String nome) { this (nome,0, 0); } Mundo (String nome, long dSol, long m) { nomeMundo=nome; distanciaSol=dSol; masa=m; creacion=new Date(); System.out.println("Creando "+nome+" en "+creacion); } String nomeMundo() { return nomeMundo; } } public class Satelite extends Mundo { long distanciaPlaneta; Mundo planeta; Satelite (Mundo p, long d, String nome) { planeta=p; distanciaPlaneta=d; nomeMundo=nome; } public static void main (String[] args) { Satelite s=new Satelite(new Mundo("Terra"),20000,"Lúa"); System.out.println("Creado o satélite "+s.nomeMundo()+" Fin."); } }
Neste exemplo convén observar a presencia de 3 versións de constructor para a clase Mundo. JAVA seleccionará automaticamente o constructor axeitado. Outra novidade e o uso do operador this, que é unha referencia ó obxecto actual. Neste caso utilízase para invocar desde un constructor a outro.
Outro exemplo, pero utilizando unha [gal:GUI|GUI]:
import java.awt.*; import javax.swing.*; public class Hola extends JFrame { Hola () { JPanel p=new JPanel(); p.add(new JLabel("Hola Mundo")); setContentPane(p); setVisible(true); } public static void main(String[] args) { new Hola(); } }
Comentarios e documentación embebida
En JAVA pódense incluír no código comentarios dunha soa liña:
// Esto é un comentario dunha liña
ou comentarios de varias liñas:</font>
/* Esto é un
comentario de
varias liñas.
*/
Pero a característica máis importante é a posibilidade de xerar documentación sobre o código a partir dos comentarios mediante unha utilidade chamada javadoc, que producirá un documento HTML. Todos os comandos de javadoc comezan por /** e finalizan con */. Existen tres tipos de documentación que corresponden co elemento ó que precede o comando: de clase, de dato ou de método. Soportan HTML embebido e unha serie de etiquetas cun significado especial :
@see nome de clase
Pode incluírse nun comentario de clase, de variable ou de método e especifica unha referencia a unha clase relacionada.
Etiquetas de clase
@version información da version
@author información do autor
Etiquetas de Método
@param nome de parámetros descrición @return descrición @throws nome completo de clase descrición @deprecated
Operadores
|
ARITMÉTICOS |
LÓXICOS |
DE BIT
|
|
+ (suma) |
== (igual que) |
& (AND bit a bit) |
|
- (resta) |
!= (distinto de) |
| (OR bit a bit) |
|
* (multiplicación) |
> (maior que) |
^ (XOR bit a bit) |
|
/ (división) |
< (menor que) |
~ (NOT bit a bit) |
|
% (resto da división) |
>= (maior ou igual que) |
<< (xiro de bits) |
|
= (asignación) |
<= (menor ou igual que) |
>> (xiro de bits) |
|
++ (autoincremento) |
&& (and) |
>>> (xiro de bits sen signo) |
|
-- (autodecremento) |
|| (or) |
|
|
|
! (not) |
|
Os operadores utilízanse unicamente cos tipos de datos primitivos a excepción do operador = e o operador +, que pode ser utilizado con obxectos de tipo String para facer concatenacións de cadeas de carácteres. Outro operador que se pode utilizar con tipos non primitivos e os de comparación == e !=, pero é moi importante ter en conta o seguinte:
En JAVA para manexar os obxectos utilízanse referencias, ou sexa que si nós definimos un novo obxecto:
Obxecto obj= new Obxecto();
o que teremos na variable obj é unha referencia ó novo obxecto creado (a súa dirección de memoria).
Si eu quero almacenar unha copia do obxecto en outra variable non será correcto facer Obxecto obj1=obj;, xa que o único que fago é almacenar en obj1 unha referencia ó mesmo obxecto que ten obj. Unha posible forma de solucionalo e construír un método de copia.
Si eu creo un segundo obxecto e os comparo, o que estou a facer é comparar dúas direccións de memoria que probablemente non é o que eu quero facer. Neste caso, o operador == unicamente será true si as dúas referencias apuntan ó mesmo obxecto, non si os obxectos son iguais (de igual contido). Do mesmo xeito != será true sempre que as dúas referencias non apunten ó mesmo obxecto. Para solucionar este problema todos os obxectos incorporan un método chamado equals, que devolve true si os dous obxectos son iguais (de igual contido) e falso si son distintos.
Exemplo:
String str1=new String("Proba 1");
String str2=new String("Proba 2");
str1.equals(str2) // ou str2.equals(str1) e devolvería false
Tanto os operadores aritméticos como a nivel de bit pódense combinar co operador de asignación (=) para abreviar (por exemplo x+=30, ou x>>=2). JAVA tomou de C os operadores de autoincremento (++) e autodecremento (--). Estes dous operadores son unarios (operan con un so termo) e aumentan en 1 ou restan 1 á variable a que acompañan:
Exemplo:
int x=0;
++x; // (x pasa a valer 1
--x; // x volve a valer 0
Si o operador antecede á variable dentro dunha expresión, primeiro efectúase o incremento ou decremento e posteriormente devólvese o valor. Se o operador vai despois da variable primeiro devólvese o valor da variable e posteriormente increméntase ou decreméntase o valor.
Exemplo:
int x=0;
System.out.print("X = ");
System.out.println(++x);
System.out.println(x++);
System.out.println(x);
Control de execución
As sentencias de control de execución son iguais que as que tan o C:
if- else e o operador ?:
if (condición) {
/* instruccións */
}
[ else {
/* instruccións */
} ]
condición ? valor1 : valor2;
(Si a condición se cumpre devolve o valor1, se non o valor2)
return
Sirve para saír dun método voltando un valor. Si o método é void indicarase unicamente return;, noutro caso terá que indicarse return valor;
while
while (condición) {
/* instruccións */
}
do-while
do {
/* instruccións */
} while (condición);
for (/*instruccións iniciais separadas por comas*/; condición; /*instruccións separadas por comas*/) {
/* instruccións a repetir */
}
for
break e continue
break fai que o programa salga dun bucle de execución e continúe forza o comezo dunha nova repetición.
switch
switch (varible de tipo primitivo) {
case valor1: /* instruccións */
break;
...
default: /* instruccións */
break;
}
Exemplo:
class Alimento {
String nome;
int calorias;
boolean carne;
Alimento (String n, int enerxia, boolean c) {
nome=n; calorias=enerxia;
carne=c;
}
boolean eCarne() {
return carne;
}
String getNome() {
return nome;
}
int getCalorias() {
return calorias;
}
}
class Dragon {
String nome;
String cor;
boolean femia;
int forza;
Dragon (String n, String c, boolean s) {
nome=n; c=cor;
femia=s; forza=0;
System.out.println("Hola, son "+nome);
}
void come (Alimento comida) {
if (!comida.eCarne())
System.out.println("Puaaaagh, "+comida.getNome());
else {
System.out.print("\n Yuuuuummm, un ");
System.out.println(comida.getNome());
forza+=comida.getCalorias();
System.out.println("ñam, ñam, ñam");
}
}
void corre() {
while (forza >= 50) {
System.out.print("/\\ \\/ /\\ ");
forza-=50;
}
System.out.println(" Bufff, estou canso. TEÑO FAME !!!");
}
}
public class Corre {
public static void main(String[] args) {
Alimento cebola=new Alimento("Cebola",3,false);
Alimento laranxa=new Alimento("Laranxa",5,false);
Alimento gusano=new Alimento("Gusano",3,true);
Alimento rato=new Alimento("Rato",5,true);
Alimento vaca=new Alimento("Vaca",300,true);
Alimento cerdo=new Alimento("Cerdo",180,true);
Alimento cabra=new Alimento("Cabra",80,true);
Dragon draco=new Dragon("Draconia","Azul",true);
draco.corre();
draco.come(cebola); draco.corre();
draco.come(gusano); draco.corre();
draco.come(rato); draco.corre();
draco.come(laranxa); draco.corre();
draco.come(cabra); draco.corre();
draco.come(cebola); draco.corre();
draco.come(vaca); draco.corre();
draco.come(cerdo); draco.corre();
draco.come(rato); draco.corre();
}
}
Táboas
As táboas son obxectos que son tratados como todos os obxectos. Para declarar unha táboa é necesario:
- Declarar unha variable para que conteña a táboa
Exemplo: Unha táboa de Strings.... String[] t;
- Crear a táboa e asignala á variable
Exemplo: creo 10 strings... t = new String[10];
Unha vez feito eso é posible almacenar valores nos distintos elementos da táboa (t[0], t[1] ...). Tamén é posible crear a táboa segundo se fai a declaración....
String[] t=new String[10];
e incluso inicializar a táboa ó mesmo tempo que se declara :
String[] semana={"Luns","Martes","Mércores","Xoves",Venres","Sábado","Domingo"};
Da mesma forma que na linguaxe C, os índices das táboas comezan por 0, e é posible declarar táboas de varias dimensións:
int[][] Coords = new int[12][12];
As táboas teñen un método length que nos devolve o número de obxectos que ten a táboa.
Exemplo:
public class Proba {
public static void main (String[] args) {
String p[][]=new String[10][2];
System.out.println(args.length+" Argumentos");
System.out.println(p.length+" P Elementos");
System.out.println(p[0].length+" P[] Elementos");
}
}
Para axudar na manipulación das táboas JAVA proporciona unha clase chamada Arrays, que ten unha serie de métodos static que permiten varias operacións de unha forma cómoda.
Como podemos observar nos exemplos anteriores, as aplicacións comezan a súa execución chamando o método main da clase invocada polo intérprete. O método main ten un argumento que é unha táboa de Strings, contendo cada elemento un parámetro dos indicados na liña de comandos. O seguinte exemplo o ilustra:
Exemplo:
public class Proba {
public static void main (String[] args) {
int i;
System.out.println(args.length+" Argumentos");
for (i=0;i<args.length;i++)
System.out.println("Arg "+i+" = "+args[i]);
}
}
Operadores this e super
this é unha palabra reservada que representa unha referencia ó obxecto actual. Normalmente utilízase para chamar a un constructor dende outro, ou para devolver unha referencia ó obxecto actual a un obxecto externo (ver exemplo na paxina 11).
super é outra palabra reservada que representa unha referencia ó obxecto pai do actual, e dicir do obxecto do que deriva o obxecto actual. A súa utilidade básica é a de chamar métodos da clase base que foron sobrepostos ou a constructores da clase base. Por exemplo, podería modificarse o exemplo da páxina 11 para non necesitar o constructor de Mundo sen argumentos.
Métodos e variables de clase e métodos e variables constantes (final).
Como xa vimos, as variables e os métodos poden ser de instancia (de obxecto) ou de clase. Normalmente tanto as variables como os métodos son de instancia, e dicir, existe unha copia por cada obxecto creado desa clase. Sen embargo é posible definir variables e métodos que existen independentemente da creación de obxectos dunha clase, e que unicamente hai unha copia para tódolos obxectos da mesma. Para facer esto unicamente é necesario antepoñer na declaración a palabra reservada static.
Para invocar os métodos ou acceder ás variables static sen necesidade de crear obxectos se poñerá antes do método ou variable o nome da clase en lugar de facelo co nome do obxecto. Un exemplo de método static que forma parte da librería estándar JAVA é println, que é un método que pertence a un obxecto static (out) da clase System, e se invoca System.out.println( ... );
Outro modificador que se pode antepoñer tanto os métodos e as variables como ás clases é final:
- Unha clase final non pode ter subclases.
- Un método final non pode ser sobreposto por ningunha clase.
- Unha variable final non pode cambiar o seu valor.
Herdanza
Cando facemos unha nova clase e a derivamos dunha xa existente herdamos tódalas súas características (os métodos e os datos membros). Para crear unha nova clase que derive de outra utilizaremos a palabra reservada extends (ver exemplo en páxina 5).
Sobreposición e sobrecarga de métodos
Pode ser que unha vez que temos unha clase derivada non nos convenza o xeito de funcionar dalgún método da clase base, de forma que é posible definir na clase derivada un novo método cunha definición exactamente igual que a da clase base. Esto denomínase sobreposición de métodos.
Cando nos chamemos o método da nova clase chamarase o método sobreposto, quedando oculto o da clase base. Sen embargo, é posible utilizar a palabra reservada super para acceder os métodos da clase base.
Exemplo:
class Base {
void metodo() {
System.out.println(" Clase Base..");
}
void metodo1() {
System.out.println(" Base metodo1..");
}
void metodo2() {
System.out.println(" Base metodo2 ..");
}
}
class Derivada extends Base {
void metodo() {
System.out.println(" Clase Derivada ..");
}
void metodo1() {
System.out.print(" Derivada metodo1 ->");
super.metodo1();
}
}
public class Sobreposicion {
public static void main(String[] args) {
Derivada d=new Derivada();
d.metodo();
d.metodo1();
d.metodo2();
}
}
Un caso distinto é cando dentro da mesma clase temos varias versións distintas dun mesmo método diferindo unicamente nos argumentos. Esto denomínase sobrecarga de métodos.
Constructores en JAVA (constructores por defecto)
Como xa vimos anteriormente, todas as clases dispoñen dun método especial que se invoca automaticamente ó crear un obxecto desa clase, o método constructor. O método constructor non especifica ningún valor de retorno e ten que chamarse exactamente igual que a clase e normalmente inicializa os datos do obxecto. Se nós non escribimos un constructor para a nosa clase, JAVA xerará un sen argumentos automaticamente, é o constructor por defecto.
Unha práctica moi común é sobrecargar os métodos constructores de xeito que dispoñamos de varias formas de crear obxectos dunha clase, pero para non repetir código sería bo que dende un método constructor se puidera chamar a outro. Esto faise co operador this como ilustra o exemplo da páxina 11.
Tamén resulta útil invocar directamente ó constructor da clase base dende o constructor da clase derivada a través do operador super.
O exemplo da páxina 6 amosa a orde en que JAVA vai invocando ós constructores dunha clase cando se crea un obxecto, e o gráfico da páxina 4 indica como se fai para localizar o método que se ten que invocar.
Destructores en JAVA. O recolector de lixo
JAVA libera a memoria automaticamente cando o sistema o precise mediante o chamado recolector de lixo, co que non precisa dos destructores típicos noutras linguaxes orientadas a obxecto, sen embargo unicamente libera os obxectos creados con new. Para poder realizar labores de limpeza cando os obxectos sexan destruídos se proporciona para todos os obxectos un método chamado finalize() que se invocará cando o obxecto sexa reclamado por primeira vez polo colector de lixo. O problema é que non se pode saber si un obxecto será destruído ou non.
Para limpar un obxecto, o usuario do mesmo debería crear e chamar a un método de limpeza no momento en que desexe, xa que non se pode un fiar de que se chamen realmente os métodos finalize().
JAVA dispón dos seguintes métodos para forzar a execución dos métodos finalize:
System.runFinalization(); Chama os métodos finalize dos obxectos pendentes de finalización
System.gc(); Forza a execución do recolector de lixo.
Os packages
JAVA introduce o concepto de espacio de nomes coa idea de garantir que as clases que se van a utilizar son en realidade as que queremos. Cando creamos un ficheiro fonte, este debe ter extensión .java que se transforma en un ficheiro .class por cada clase utilizada. Unha aplicación constará de moitos .class que o intérprete de JAVA terá que atopar, cargar e interpretar. As librerías son tamén un conxunto de ficheiros .class, si se quere indicar que unha serie de ficheiros .class van xuntos indícase coa palabra package o inicio do ficheiro:
package 3Drotations;
Para referirnos posteriormente a unha clase dun package é necesario indicalo con notación de puntos.
3Drotations r=new 3Drotations.Translate();
Para evitar esto é posible importar o package completo e posteriormente referirnos ás clases de forma simple (ver exemplo páxina 12):
import 3Drotations.*;
Translate r=new Translate();
É posible tamén importar unha única clase do package .. import 3Drotations.Translate;
Para que podan convivir dun xeito organizado varias versións dunha mesma clase é preciso seguir un estándar para os nomes dos packages. JAVA é unha linguaxe orientada a Internet, e polo tanto e posible que varias aplicacións utilicen versións distintas dunha clase con gran facilidade. O estándar para os nomes de packages que garante que sexan únicos é o seguinte:
- Cada nome do package en notación de puntos corresponde cun directorio dentro da carpeta da librería JAVA
- Debería chamarse o package co nome de dominio do autor de forma inversa, seguido pola identificación do package.
Por exemplo: gz.xavi.graficos.poligonos.
Especificadores de acceso (public, private, protected e friendly)
Como xa vimos, en JAVA os métodos e os datos membro poden ser public, private, protected ou friendly mentres que as clases poden ser unicamente public ou non public.
public : Todas as outras clases teñen acceso os métodos ou membros public.
private : Unicamente se pode acceder os métodos ou membros private desde dentro da propia clase.
protected : Unicamente se pode acceder os métodos ou membros protected desde dentro da propia clase ou desde unha clase herdada.
friendly : E o acceso por defecto e non se especifica. Unicamente se pode acceder os métodos ou membros friendly desde as clases desenvolvidas no mesmo package.
Clases abstractas e Interfaces
Un método abstracto é un método que unicamente ten definido o seu valor de retorno, nome e argumentos, non o corpo do método. Unha clase abstracta é unha clase que ten métodos abstractos. Non é posible definir obxectos de clases abstractas, se non que é necesario derivar unha nova clase e implantar o método ou métodos abstractos.
As clases abstractas utilízanse para forzar a existencia dun método dentro de clases derivadas de este, pero que a clase base non pode implantar por ser demasiado xeral. Para definir un método abstracto utilízase abstract:
abstract void calculaArea(int[] nums);
Un interface é unha clase abstracta 'pura', e dicir, non ten implantado ningún dos seus métodos. Os interfaces defínense coa palabra clave interface no lugar de class, e os datos membro que conteña serán static e final e os métodos public.
A diferencia das clases, dun interface non se poden derivar novas clases, se non que é necesario definir unha clase que o implante, e dicir, que teña definidas todos os métodos do mesmo. Para facer esto emprégase a palabra reservada implements. Unha clase pode derivarse doutra (extends) e á vez implantar outra (implements).
'Herdanza múltiple' con interfaces
JAVA é unha linguaxe de herdanza simple, o que quere dicir que unha clase unicamente pode ter unha clase base, sen embargo este límite non se da a hora de implantar interfaces. Unha clase pode estender outra e implantar varios interfaces o mesmo tempo (ou simplemente implantar varios interfaces).
Exemplo:
interface PodeVoar {
void voa();
}
interface PodeNadar {
void nada();
}
interface PodeCorrer {
void corre();
}
interface PodeFalar {
void fala(String str);
}
class Print implements PodeFalar {
public void fala(String str) {
System.out.println(str);
}
}
public class Pato extends Print implements PodeVoar, PodeNadar, PodeCorrer {
public void voa() {
fala("voando...");
}
public void nada() {
fala("nadando...");
}
public void corre() {
fala("correndo...");
}
public static void main(String[] args) {
Pato p=new Pato();
p.voa(); p.corre(); p.nada();
}
}
Contedores de obxectos e Iteradores.
Os contedores de obxectos son unha serie de clases que serven para almacenar obxectos de calquera clase e acceder posteriormente ós obxectos almacenados. A cantidade de obxectos que se poden almacenar nun container depende unicamente da memoria dispoñible. Algúns dos containers dispoñibles son: Vector, ArrayList, LinkedList, HashSet, TreeSet, HashMap, TreeMap ...
Da mesma forma que Arrays para as táboas temos tamén unha clase con métodos estáticos para utilizar cos containers Collections.
O principal problema dos containers é que o almacenar obxectos de tipo Object, e necesario converter posteriormente os obxectos contidos ó tipo orixinal, esto faise con casting, é dicir indicando (a clase) antes do obxecto.
Rect r= (Rect) p; // sendo p de tipo Object
Moitas veces necesitaremos ademais coñecer de qué tipo concreto é un obxecto, para elo temos o operador instanceof que devolve true si o obxecto é unha instancia da clase indicada:
if (r instanceof Rect) ....
Imaxe:CURSO html 78c0a249.gif
En calquera clase de container debemos ter unha forma de poñer obxectos dentro do container e unha forma de sacalos. Unha forma estándar de facelo para tódolos containers é a través dun iterador. Un iterador é un obxecto que se pode mover a través de secuencias de obxectos e seleccionalos sen que haxa que preocuparse polo tipo de container no que están almacenados. Un exemplo é a Iterator, pódese obter un Iterator chamando ó método iterator() do contedor, e disporemos de funcións para eliminar obxectos ou pasar o seguinte.
Clases de containers, Iteradores e comparadores.
Excepcións
As excepcións son o tratamento de erros da linguaxe, e descenden das clases Error ou Exception que a súa vez descenden de Throwable. As clases descendentes de Error son para erros moi graves que normalmente son manexados polo propio linguaxe, o interesante reside en Exception.
A clase Exception ten as subclases en dous grupos xerais: RuntimeException, ou excepcións en tempo de execución como ArrayIndexOutofBounds, SecurityException ou NullPointerException e outras como EOFException, FileNotFoundException ou MalformedURLException. As excepcións en tempo de execución se producen normalmente por incorrección no código.
Cando se utilizan métodos que lanzan excepcións JAVA obriga a manexalas mediante a instruccións try.
Exemplo:
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
/* tratamento do erro */
System.out.println("Erro "+e.getMessage());
}
No exemplo, o método sleep pode lanzar a excepción InterruptedException, co que é necesario manexala. Con catch indicamos o que ten que facer o programa si se produce a excepción, como se pode observar a catch séguelle un parámetro que é unha variable do tipo de excepción a tratar.
Tamén é posible escribir varios bloques catch para capturar varias excepcións. Levarase a cabo o primeiro bloque que concorde coa excepción producida.
Outra instrucción que se combina con try é finally, que pode colocarse se use catch ou non. finally asegura que despois das instruccións incluídas no bloque try se leve a cabo o especificado no bloque finally, polo que é moi útil para facer limpeza antes de terminar as aplicacións (pechar ficheiros por exemplo).
Tamén é posible definir métodos que lancen excepcións utilizando a cláusula throws, o que obrigaría a usar un bloque try ó utilizar o método:
public int exemplo (int parametro) throws FileNotFoundException {
}
poden indicarse varias excepcións distintas separadas por comas. Si en un método chamamos a outro que lanza excepcións, en lugar de tratala tamén podemos 'reenviala' á función que utiliza a nosa mediante unha instrucción throw. Por suposto, é posible crear novas excepcións derivando de Throwable.
Copia de obxectos. O método clone()
Como xa vimos, non é posible copiar un obxecto dun xeito sinxelo xa que traballamos con referencias ós obxectos, non cos obxectos mesmo. Para resolver esto a clase Object ten un método chamado clone() que é o encargado de facer unha copia do obxecto actual, por desgracia unicamente traballa con unhas poucas clases como ArrayList.
A copia de obxectos non está automáticamente incluída en todas as clases, se non que é necesario implementala a través do interface Cloneable. Ademais si o obxecto é composto, cada obxecto membro ten que ser Cloneable, e ter superposto o método clone.
Exemplo:
import java.util.*;
class ObxectoClonable implements Cloneable {
int i;
ObxectoClonable (int x) { i=x; }
public Object clone() {
Object o=null;
try {
o=super.clone();
}
catch (CloneNotSupportedException e) {
System.err.println("Non soporto a copia !!");
}
return o;
}
public void setVal (int x) { i=x; }
public String toString() { return Integer.toString(i); }
}
public class Clon {
public static void main(String[] args) {
ObxectoClonable obj1= new ObxectoClonable(1);
ObxectoClonable obj2;
obj2=obj1; obj2.setVal(33);
System.out.println("Obj1="+obj1);
System.out.println("Obj2="+obj2);
obj1.setVal(11); obj2=(ObxectoClonable) obj1.clone();
System.out.println("Obj1="+obj1);
System.out.println("Obj2="+obj2);
obj2.setVal(99);
System.out.println("Obj1="+obj1);
System.out.println("Obj2="+obj2);
}
}
Ficheiros
En JAVA a información pódese almacenar e recuperar mediante un sistema de comunicación chamado fluxos implantados no paquete java.io. Os fluxos poden dividirse en fluxos de bytes no que se manexan bytes, números enteiros e tipos simples de datos, e de carácteres, no que se manexan arquivos de texto e outras fontes de carácteres. En JAVA, debido ó soporte Unicode calquera clase de datos que teña texto debería ser tratado cun fluxo de carácteres. As clases utilizadas para o acceso son:
File
Exemplo: import java.io.*;
public class FileList extends File {
String[] flist;
FileList(String path) {
super(path);
flist=list();
for (int i=0;i<flist.length;i++) {
if (new File(flist[i]).isDirectory())
System.out.println(flist[i]+"\t <DIR>");
else
System.out.println(flist[i]);
}
}
public static void main(String[] args) {
new FileList(args[0]);
}
}
FileInputStream (fluxo de bytes) FileOutputStream (fluxo de bytes)
FileReader (fluxo de carácteres) FileWriter (fluxo de caracteres)
RandomAccessFile (acceso aleatorio)
Exemplo:
import java.io.*;
public class Lee {
Lee(String fich) {
int eof=0;
try {
FileReader f=new FileReader(fich);
System.out.println("Listando "+fich);
eof=f.read();
while (eof!=-1) {
System.out.print((char)eof);
eof=f.read();
}
f.close();
} catch (FileNotFoundException e) {
System.out.println("Ficheiro non atopado!!");
}
catch (IOException e) {
System.out.println("Erro o abrir o Ficheiro!!");
}
}
public static void main(String[] args) {
new Lee(args[0]);
}
}
E dispoñemos das seguintes clases para facer filtrado
BufferedInputStream BufferedOutputStream
BufferedReader BufferedWriter
DataInputStream DataOutputStream
O filtrado consiste en engadirlle novas características a un fluxo de datos que foi aberto mediante unha operacion File*. Por exemplo, Buffered* proporciona acceso a través dun buffer en memoria e Data* proporciona a posibilidade de acceder os datos coma si foran tipos primitivos JAVA en lugar de facelo como bytes ou caracteres. Poden combinarse varios filtrados en cascada.
Exemplo: import java.io.*;
public class Lee {
Lee(String fich) {
int eof=0;
try {
FileReader f=new FileReader(fich);
BufferedReader f1=new BufferedReader(f);
System.out.println("Listando "+fich);
eof=f1.read();
while (eof!=-1) {
System.out.print((char)eof);
eof=f1.read();
}
f1.close();
} catch (FileNotFoundException e) {
System.out.println("Ficheiro non atopado!!");
} catch (IOException e) {
System.out.println("Erro o abrir o Ficheiro!!");
}
}
public static void main(String[] args) {
new Lee(args[0]);
}
}
Threads
Os threads ou fíos de execución indican que unha tarefa ten funcionando 'simultaneamente' diversas partes. A forma máis sinxela de crear threads é derivando da clase Thread:
Exemplo:
public class Tarefa extends Thread {
private int id;
Tarefa(int n) {
id=n;
System.out.println("Creando Tarefa "+id);
}
public void run () {
int i=0;
while(true) {
System.out.println("Tarefa "+id+" = "+i++);
}
}
public static void main(String[] args) {
new Tarefa(0).start();
new Tarefa(1).start();
new Tarefa(2).start();
new Tarefa(3).start();
}
}
Unha forma alternativa de facelo é implementando a interface Runnable, e posteriormente crear un obxecto Thread e chamar ó método start();
Exemplo:
public class Tarefa implements Runnable {
private int id;
Tarefa(int n) {
id=n;
System.out.println("Creando Tarefa "+id);
new Thread(this).start();
}
public void run () {
int i=0;
while(true) {
System.out.println("Tarefa "+id+" = "+i++);
}
}
public static void main(String args[]) {
new Tarefa(0);
new Tarefa(1);
new Tarefa(2);
new Tarefa(3);
}
}
CREACIÓN DE APPLETS
JAVA debe a meirande parte da súa popularidade a posibilidade de incluír aplicacións dentro dunha páxina WEB. A maior parte dos navegadores WEB dispoñen dunha máquina virtual JAVA que é capaz de interpretar e executar byte-code JAVA. O código JAVA incluído dentro dunha páxina WEB denomínase applet.
Como os applets execútanse dentro do sistema do usuario existen unha serie de restriccións de seguridade sobre o que un applet JAVA pode facer dentro do sistema do usuario:
- Un applet non pode ler nin escribir arquivos no sistema do usuario
- Un applet non pode comunicarse cun sitio de Internet distinto do que serviu a páxina WEB que incluía o applet.
- As ventás despregadas por un applet amosan sempre unha mensaxe 'Ventá de applet de JAVA'.
Sen embargo as máquinas virtuais dos navegadores poden permitir configurar niveis de seguridade habilitando algunha ou todas as características que non funcionan normalmente nos applets. Outra posibilidade é o uso de firmas dixitais para habilitar ós applets unha funcionalidade completa.
Unha firma dixital é un arquivo encriptado que acompaña a un programa, indicando de quen ven (certificado). O proceso para firmar un applet é o seguinte:
- Debemos xerar dous arquivos encriptados coa utilidade keytool do JDK, un que se denomina clave pública e o outro clave privada. A clave privada almacénase de forma que ninguén poda acceder a ela, e a clave pública faise dispoñible a todo o mundo.
- Necesitamos unha empresa imparcial que verifique a nosa identidade (por exemplo VeriSign ou Thawte Certification). Mandámoslle a nosa clave pública e un arquivo descritivo da nosa compañía.
- A compañía de seguridade comproba que a nosa compañía é quen di ser, crea un novo arquivo encriptado, o certificado e nolo envía.
- Mediante a utilidade jar e clave privada e o certificado firmamos o applet.
- Cando descarguen a páxina WEB co applet daremos instruccións para descargar a clave pública.
A necesidade da clave pública e privada radica en que ninguén poda firmar applets usando o noso certificado. A clave pública sirve para que a compañía certificadora verifique a nosa identidade e para comprobar o certificado, e a clave privada utilízase para certificar os applets.
O usuario do applet atopará a seguinte situación:
- Recíbese un aviso de que non se pode executar o applet sen establecer a nosa compañía como fiable, e se descarga a clave pública.
- Unha vez que se decide confiar no applet é posible utilizar a ferramenta do JDK jarsigner para agregar a compañía a unha lista de compañías fiables.
Sen embargo, hoxe en día o único modo de firmar un applet de forma efectiva é seguindo os procedementos establecidos pola compañía a que pertence o navegador, si se quere poder utilizar varios navegadores é necesario firmar o applet para todos eles.
Vexamos un exemplo de applet:
import java.applet.*;
import java.awt.*;
public class HolaMundo extends Applet {
public void paint (Graphics g) {
g.drawString("Hola Mundo !!!");
}
}

