
Pedro Asencio; esto no es la gran cosa en lo que respecta a Java pero te servira como introducción. Alguna otra duda escribeme a: pasencio16@yahoo.com
Cuando se programa en Java, se coloca todo el código en métodos, de la misma forma que se escriben funciones en lenguajes como C.
En Java hay tres tipos de comentarios:
// comentarios para una sola línea
/* comentarios de una o
más líneas
*/
/** comentario de documentación, de una o más líneas
*/
Los dos primeros tipos de comentarios son los que todo programador conoce y se utilizan del mismo modo. Los comentarios de documentación, colocados inmediatamente antes de una declaración (de variable o función), indican que ese comentario ha de ser colocado en la documentación que se genera automáticamente cuando se utiliza la herramienta de Java, javadoc. Dichos comentarios sirven como descripción del elemento declarado permitiendo generar una documentación de nuestras clases escrita al mismo tiempo que se genera el código.
En este tipo de comentario para documentación, se permite la introducción de algunos
tokens o palabras clave, que harán que la información que les sigue aparezca de forma
diferente al resto en la documentación.
Los identificadores nombran variables, funciones, clases y objetos; cualquier cosa que el programador necesite identificar o usar.
En Java, un identificador comienza con una letra, un subrayado (_) o un símbolo de dólar ($). Los siguientes caracteres pueden ser letras o dígitos. Se distinguen las mayúsculas de las minúsculas y no hay longitud máxima.
Serían identificadores válidos:
identificador
nombre_usuario
Nombre_Usuario
_variable_del_sistema
$transaccion
y su uso sería, por ejemplo:
int contador_principal;
char _lista_de_ficheros;
float $cantidad_en_Ptas;
Palabras clave
Las siguientes son las palabras clave que están definidas en Java y que no se pueden
utilizar como indentificadores:
abstract continue for new switch
boolean default goto null synchronized
break do if package this
byte double implements private threadsafe
byvalue else import protected throw
case extends instanceof public transient
catch false int return true
char final interface short try
class finally long static void
const float native super while
Palabras Reservadas
Además, el lenguaje se reserva unas cuantas palabras más, pero que hasta ahora no tienen
un cometido específico. Son:
cast future generic inner
operator outer rest var
Un valor constante en Java se crea utilizando una representación literal de él. Java utiliza cinco tipos de elementos: enteros, reales en coma flotante, booleanos, caracteres y cadenas, que se pueden poner en cualquier lugar del código fuente de Java. Cada uno de estos literales tiene un tipo correspondiente asociado con él.
Enteros:
byte 8 bits complemento a dos
short 16 bits complemento a dos
int 32 bits complemento a dos
long 64 bits complemento a dos
Por ejemplo: 21 077 0xDC00
Reales en coma flotante:
float 32 bits IEEE 754
double 64 bits IEEE 754
Por ejemplo: 3.14 2e12 3.1E12
Booleanos:
true
false
Caracteres:
Por ejemplo: a \t \u???? [????] es un número unicode
Cadenas:
Por ejemplo: "Esto es una cadena literal"
Se pueden declarar en Java arrays de cualquier tipo:
char s[];
int iArray[];
Incluso se pueden construir arrays de arrays:
int tabla[][] = new int[4][5];
Los límites de los arrays se comprueban en tiempo de ejecución para evitar desbordamientos y la corrupción de memoria.
En Java un array es realmente un objeto, porque tiene redefinido el operador []. Tiene una función miembro: length. Se puede utilizar este método para conocer la longitud de cualquier array.
int a[][] = new int[10][3];
a.length; /* 10 */
a[0].length; /* 3 */
Para crear un array en Java hay dos métodos básicos. Crear un array vacío:
int lista[] = new int[50];
o se puede crear ya el array con sus valores iniciales:
String nombres[] = {
"Juan","Pepe","Pedro","Maria"
};
Esto que es equivalente a:
String nombres[];
nombres = new String[4];
nombres[0] = new String( "Juan" );
nombres[1] = new String( "Pepe" );
nombres[2] = new String( "Pedro" );
nombres[3] = new String( "Maria" );
No se pueden crear arrays estáticos en tiempo de compilación:
int lista[50]; // generará un error en tiempo de compilación
Tampoco se puede rellenar un array sin declarar el tamaño con el operador new:
int lista[];
for( int i=0; i < 9; i++ )
lista[i] = i;
Es decir, todos los arrays en Java son estáticos. Para convertir un array en el
equivalente a un array dinámico en C/C++, se usa la clase vector, que permite
operaciones de inserción, borrado, etc. en el array.
Los operadores de Java son muy parecidos en estilo y funcionamiento a los de C. En la siguiente tabla aparecen los operadores que se utilizan en Java, por orden de precedencia:
. [] ()
++ --
! ~ instanceof
* / %
+ -
<< >> >>>
< > <= >= == !=
& ^ |
&& ||
? :
= op= (*= /= %= += -= etc.) ,
Los operadores numéricos se comportan como esperamos:
int + int = int
Los operadores relacionales devuelven un valor booleano.
Para las cadenas, se pueden utilizar los operadores relacionales para comparaciones además de + y += para la concatenación:
String nombre = "nombre" + "Apellido";
El operador = siempre hace copias de objetos, marcando los antiguos para borrarlos, y ya se encargará el garbage collector de devolver al sistema la memoria ocupada por el objeto eliminado.
Sólo hay un par de secuencias con otros caracteres que pueden aparecer en el código
Java; son los separadores simples, que van a definir la forma y función del código. Los
separadores admitidos en Java son:
() - paréntesis. Para contener listas de parámetros en la definición y llamada a métodos. También se utiliza para definir precedencia en expresiones, contener expresiones para control de flujo y rodear las conversiones de tipo.
{} - llaves. Para contener los valores de matrices inicializadas automáticamente. También se utiliza para definir un bloque de código, para clases, métodos y ámbitos locales.
[] - corchetes. Para declarar tipos matriz. También se utiliza cuando se referencian valores de matriz.
; - punto y coma. Separa sentencias.
, - coma. Separa identificadores consecutivos en una declaración de variables. También se utiliza para encadenar sentencias dentro de una sentencia for.
. - punto. Para separar nombres de paquete de subpaquetes y clases. También se utiliza para separar una variable o método de una variable de referencia.
Como mencionamos anteriormente, Java dispone de una serie de librerías
(paquetes) estándar que ofrecen clases para las necesidades más comunes, como se puede
ver en la Tabla A. En este artículo abordaremos el soporte de Java para
operaciones de entrada y salida, así como para la gestión del sistema de archivos, que
se encuentra en el paquete java.io. Cuando pensamos en las operaciones de entrada y
salida, se suele pensar en dos tipos de operaciones: operaciones de lectura/escritura
sobre archivos, y operaciones de introducción de datos mediante el teclado. Si bien esto
resulta muy común, a la hora de la verdad, una aplicación puede obtener datos de muchas
otras formas: a través de una conexión vía Internet con un servidor remoto, a través
de una conexión DDE con otro programa en la misma máquina, o incluso a través del
portapapeles de Windows. Se puede ver que, a pesar de la distinta procedencia de la
información en todos estos casos, su manejo será bastante similar: solicitamos al
sistema que nos conecte a la fuente o destino de la información (abrimos archivo, nos
conectamos al servidor de red, etc.), obtenemos la información, que será una serie de
datos secuenciales, y nos desconectamos de la fuente de datos, para liberar recursos del
sistema. Java utiliza el concepto de flujo (stream) para trabajar con información
manejada secuencialmente.
| java.applet | Librería para creación y manejo de applets |
| java.awt | Librería de interface gráfico |
| java.io | Librería para operaciones de Entrada/Salida |
| java.lang | Librería con clases básicas |
| java.net | Librería de soporte para programación en red |
| java.util | Clases de utilidad, como pilas, etc. |
Tabla A: Las librerías estándar de Java.
No siempre se puede o es conveniente trabajar secuencialmente: hay ocasiones en que
deseamos tener acceso aleatorio, en lugar de leerla en serie hasta que llegamos a la
posición donde está la información que deseamos. Java también proporciona soporte para
acceso aleatorio a archivos, a través de la clase RandomAccessFile. El acceso al
sistema de archivos del sistema también es fundamental: es necesario poder renombrar
archivos, obtener la lista de archivos de un directorio, saber si un archivo es de
escritura o lectura, etc. Java proporciona la clase File para manejar el sistema de
archivos, independientemente de las clases basadas en flujos y de acceso aleatorio que
utiliza para leer/escribir en ellos. La Tabla B muestra las clases de entrada y
salida que se pueden encontrar en java.io.
| InputStream ByteArrayInputStream FileInputStream PipedInputStream SequenceInputStream StringBufferInputStream FilterInputStream BufferedInputStream DataInputStream LineNumberInputStream PushbackInputStream OutputStream ByteArrayOutputStream FileOutputStream PipedOutputStream FilterOutputStream BufferedOutputStream DataOutputStream PrintStream File FileDescriptor RandomAccessFile StreamTokenizer |
Tabla B: Clases en el paquete java.io
Por último, Java proporciona una clase de excepción para cada tipo de error común en
las operaciones de entrada y salida: cada una de estas clases deriva de la clase base IOException.
Abordaremos cada una de estas partes del paquete java.io por separado.
El concepto de flujo es muy potente, dado que proporciona un modo de tratar las
operaciones de entrada/salida de forma similar para distintas fuentes de datos y canales
de comunicación. Podemos definir un flujo como una secuencia de bytes que viajan desde
una fuente a un destino a través de un camino, de modo secuencial. Java proporciona un
conjunto de clases para leer información desde un flujo, y otro para escribir en él. Las
dos clases fundamentales son InputStream y OutputStream, para lectura y
escritura respectivamente, que proporcionan métodos para realizar las operaciones
básicas: en función de las distintas fuentes o modos de manejar la información se
tendrán distintas clases derivadas de éstas, siempre respetando el protocolo básico
dictado por ellas. El esquema básico de trabajo con los flujos es siempre el mismo: se
abren, lo que se consigue con las operaciones new InputStreamFile(...), etc., se
realizan las operaciones deseadas de escritura y lectura con read, write,
etc., y luego se cierran, con close(). El Listado A muestra un programa que
lee un archivo y lo copia en otro, utilizando flujos. Nótese que utilizamos las clases FileInputStream
y FileOutputStream, derivadas de InputStream y OutputStream y con los
mismo métodos.
| import java.io.*; public class entrada_salida1 { // Creamos y abrimos los flujos // Realizamos operaciones de entrada y salida // Cerramos los flujos |
Listado A: Programa que copia un archivo en otro (entrada_salida1.java)
Vale la pena destacar un par de puntos en el Listado A: en primer lugar, en
cualquier punto del programa se puede producir un error de entrada/salida, del tipo IOException,
motivo por el que lo hemos indicado en la primera línea del método main, mediante
el código throws IOException: no hay peligro de olvidar esto, porque Java se
negará a compilar, como ya vimos en el artículo de Marzo sobre excepciones. Otro punto
importante es que hemos creado objetos de las clases FileInputStream y FileOutputStream,
pero los hemos asignado a variables de las clases InputStream y OutputStream:
esto funciona debido al polimorfismo. Dado que las clases FileXXXStream son
derivadas de las clases XXXStream, se llamará al método adecuado de FileXXXStream,
clases a las que realmente pertenecen los objeto asignados a la variable. Además de
funcionar, este modo de codificar es conveniente: así, si deseamos copiar el archivo a
pantalla, bastará con asignar a salida un flujo que sea capaz de escribir en
pantalla, en lugar de en un archivo, como puede ser System.out, que ya hemos
utilizado en otros artículos, y que es un objeto de la clase PrintStream, derivada
de OutputStream. Bastará con escribir OutputStream salida =
System.out; en lugar de OutputStream salida = new
FileOutputStream...; sin modificar ningún otro fragmento de código (se puede encontrar
el código fuente en el archivo entrada_salida2.java incluido en el disco).
Alternativamente, en lugar de enviar la información de salida a la pantalla podríamos
escribir en memoria compartida entre varios procesos, a una conexión remota, o a casi
cualquier cosa capaz de almacenar información, siempre que tengamos una clase XXXStream
adecuada. En el Listado B se pueden ver los métodos de OutputStream y en la
Listado C los de InputStream. Estudiaremos estas dos clases básicas en
detalle, y luego las clases derivadas, explicando en qué se diferencian y qué añaden.
Todo lo que sepamos de las clases base se cumplirá también para las derivadas: al fin y
al cabo, en Java cuando derivamos de una clase estamos comprometiéndonos a que la clase
derivada se comporte como esta, posiblemente añadiendo nuevas capacidades.
| public abstract class java.io.OutputStream extends java.lang.Object { // Constructores public OutputStream(); // Métodos |
Listado B: La clase base para flujos de salida/escritura, OutputStream
La clase OutputStream proporciona varios métodos para escritura, todos llamados write
(en el número de Febrero comentamos que Java soportaba la sobrecarga, es decir, tener
varios métodos con el mismo nombre). La primera versión del listado proporciona la
posibilidad de escribir varios bytes a la vez (un array), así como el segundo, que
permite indicar qué parte del array es la que deseamos tratar: comienzo es el
lugar desde el que deseamos comenzar a copiar, y l el número de bytes a partir de
dicha posición. Por fin, la última versión de write, que es la que hemos
utilizado en nuestro programa, simplemente escribe un entero. Otro método importante es close,
que cierra el flujo, y que es importante no olvidar si deseamos que no se pierda
información inadvertidamente. En cuanto a flush, es un método destinado a
asegurarse de que realmente se guarda la información: muy a menudo ésta no va a parar
directamente al lugar de destino, sino que se guarda en memoria intermedia, de modo que se
escriba todo un bloque de información de una vez para evitar continuos accesos al lugar
de destino, haciendo así más rápido el proceso. Esto, sin embargo, puede hacer que
perdamos información si se cae el sistema, motivo por el que existe flush, que
fuerza la escritura rea. Como se puede ver, OutputStream es una clase abstracta,
destinada a ofrecer un protocolo estándar a todos los flujos de escritura, y nada más:
cada clase derivada se encarga de implementar cada método de la manera más adecuada al
destino de la información y al canal utilizado para transmitirla.
| public abstract class java.io.InputStream extends java.lang.Object { // Constructores public InputStream(); // Métodos public long skip(long n); public void mark(int limiteLectura); |
Listado C: La clase base de entrada/lectura, InputStream
La clase InputStream es la contraparte de OutputStream utilizada para
lectura. Para leer del flujo contamos con tres versiones del método read: la
primera versión en el Listado C lee un entero, y la segunda y la tercera un array
de bytes, devolviendo el número de bytes leídos. Todos estos métodos devuelven -1 para
indicar que se ha encontrado el final del flujo, y no hay más datos a recuperar. Al igual
que con los flujos de salida, es necesario cerrar un InputStream una vez que hemos
terminado de utilizarlo, mediante close. Aparte de estos métodos, tenemos skip,
que avanza n bytes en el flujo de entrada, saltándoselos, y available, que
determina el número de bytes que se pueden leer. Nótese que es posible que available
devuelva 0 siempre para cierto tipo de flujos en algunos sistemas, por lo que se ha de
tener precaución a la hora de utilizarlo. Otra posibilidad interesante en un flujo es la
capacidad de recordar la posición donde hemos estado en un momento dado, para luego
volver a ella: esto se implementa mediante mark, que memoriza la posición actual,
método al que se le pasa como parámetro el número de bytes que se pueden leer sin que
el marcador quede invalidado. El método reset nos devuelve a la última posición
marcada. Es posible que determinados tipos de flujos no soporten la posibilidad de marcar
cierta posición y volver a ella: por ejemplo, se podría plantear si tiene sentido volver
a una posición anterior en un flujo de entrada asociado a la entrada por teclado. Para
averiguar si cierto flujo soporta o no el uso de marcadores se puede utilizar markSupported.
Nótese que el hecho de que no se pueda garantizar la validez del uso de marcadores no es
un problema de la implementación en Java, sino un reflejo de la diversidad de los
dispositivos y de los que se puede obtener información de entrada en el mundo real, cada
uno con sus distintas capacidades.
Como hemos visto en el Listado A, Java soporta la
escritura/lectura de archivos mediante las clases FileOutputStream y FileInputStream,
derivadas de OutputStream e InputStream, respectivamente. Absolutamente
todos los métodos que hemos visto para las clases base de manejo de flujos funcionan tal
y como se vio, por lo que solo expondremos las novedades que presentan estas clases, o las
pequeñas variaciones que puedan tener algunos métodos. El Listado D muestra
los nuevos métodos y constructores de FileOutputStream. Evidentemente, para
construir un flujo que funcione sobre un archivo, habrá que especificar de algún modo el
archivo: el mejor lugar para ello es el constructor del flujo. La clase proporciona tres
constructores para especificar el archivo: el primero en el listado permite indicarlo
simplemente especificando el nombre. El segundo constructor recibe como parámetro un
objeto de la clase File, utilizado por Java para representar los archivos y
manejarlos (renombrarlos, eliminarlos, etc.), y cuyo estudio abordaremos más adelante. El
tercer constructor toma como parámetro un FileDescriptor, que es otra clase
utilizada para representar un archivo: la estudiaremos junto con File. Por último,
tenemos un único método nuevo, getFD, que simplemente devuelve el FileDescriptor
asociado al archivo sobre el que trabaja el flujo.
| public class java.io.FileOutputStream extends java.io.OutputStream { // Constructores public FileOutputStream(String nombreArchivo); public FileOutputStream(File archivo); public FileOutputStream(FileDescriptor fd); // Métodos public final FileDescriptor getFD(); // ... } |
Listado D: Métodos que FileOutputStream añade con respecto a OutputStream.
En cuanto a FileInputStream, añade exactamente los mismos constructores y métodos
con respecto a InputStream que FileOutputStream con respecto a OutputStream.
Es posible que deseemos manejar a veces un buffer en memoria (array) o
una cadena de texto como un flujo. Aunque en principio puede parecer muy extraño querer
manejar una cadena de este modo, esto nos permite escribir código para tratar del mismo
modo cadenas, bloques de memoria o archivos. Si, por ejemplo, nos construimos un pequeño
intérprete que analice código escrito en un archivo, ¿por qué no escribirlo basándose
en las clases de flujo, de modo que se pueda también interpretar información escrita
directamente por el usuario, y que el programa obtiene de él como una cadena?. De este
modo, ahorraríamos el trabajo de escribir la cadena introducida por el usuario en un
archivo, y obtendríamos una mejora de velocidad, al no tener que pasar el código a
disco. Las clases que proporciona Java para esto son ByteArrayInputStream, ByteArrayOutputStream
y StringBufferInputStream. ByteArrayInputStream, en el Listado E, no
añade ningún nuevo método a InputStream, clase de la que, como FileInputStream,
deriva. Eso sí, el método available está garantizado que devuelve el número de
bytes en memoria, y además existe la particularidad de que reset nos lleva al
comienzo del buffer, en lugar de a un marcador guardado con mark. Como un ByteArrayInputStream
se construye sobre un array de bytes, necesitaremos constructores que tengan en cuenta
este hecho: el primero del listado permite especificar el array del que el flujo obtiene
los datos, mientras que el segundo especifica el array, pero solo una parte del mismo,
indicando esto a través de los parámetros c, la posición de comienzo, y l,
el número de bytes a tener en cuenta a partir de la posición de comienzo. La clase StringBufferStream
es idéntica a ésta, salvo que en lugar de obtener la información de un array de bytes,
la obtenemos de una cadena ( un StringBuffer).
| public class java.io.ByteArrayInputStream extends java.io.InputStream { // Constructores public ByteArrayInputStream(byte buf[]); public ByteArrayInputStream(byte buf[], int c, int l ); // ... } |
Listado E: Métodos que ByteArrayInputStream añade a su clase base, InputStream
En cuanto a ByteArrayOutputStream, cuyos nuevos métodos se pueden encontrar en el Listado
F, es un buffer dinámico, que crece conforme le vamos añadiendo datos. Se le puede
especificar un tamaño base, como se puede ver en el primer constructor del listado, o
bien dejar que tenga un tamaño inicial por defecto, utilizando el segundo constructor. La
clase ByteArrayOutputStream ofrece varios métodos nuevos con respecto a OutputStream.
Es posible saber el número de bytes que se han escrito mediante size. Además, es
posible obtener un array con los datos del flujo, mediante toByteArray, o cadenas
de texto, mediante las dos versiones de toString en el Listado F. Esto
último puede ser útil, dado que resulta muy común que lo que manejemos en memoria no
sea más que una cadena de texto. Como una comodidad adicional, existe la posibilidad de
pasar toda la información almacenada en la memoria por el flujo a otro flujo de salida,
como un archivo, etc., mediante el método writeTo( flujoSalida).
| public class java.io.ByteArrayOutputStream extends java.io.OutputStream { // Constructores public ByteArrayOutputStream(int tamanyo); public ByteArrayOutputStream(); // Métodos public int size(); public byte[] toByteArray(); public String toString(); public String toString(int hibyte); public void writeTo(OutputStream os); } |
Listado F: Métodos que ByteArrayOutputStream añade a OutputStream.
Además de las clases vistas hasta ahora, Java proporciona unos flujos especiales para comunicación entre threads: la ventaja de utilizar este modo de comunicación es que Java se encargará de todas las tareas de sincronización en el acceso a los datos, de modo que los procesos lectores y escritores no choquen. Las clases utilizadas para llevar a cabo esta tarea son PipedOutputStream y PipedInputStream. La idea básica aquí es que tenemos un objeto de cada clase, y los threads lectores usan el de la clase PipedInputStream, y los procesos escritores el de la clase PipedOutputStream, a través de los cuales se accede a una misma información: para ponerlos de acuerdo en que esto es así, hay que conectar el flujo de entrada con el de salida, lo que se hace mediante código como el que sigue: pipeEntrada.connect( pipeSalida ); o bien pipeSalida.connect( pipeEntrada ); con lo cuál ambos trabajarán sobre la misma información. Como se puede ver, el método connect existe para ambas clases, y es el único método que añaden a sus clases base, que como de costumbre son InputStream y OuputStream. Además, la operación de poner de acuerdo a ambos flujos también se puede llevar a cabo mediante un constructor que proporcionan y que permite pasar como parámetro el pipe complementario, lo que hace innecesario llamar a connect.
Java proporciona una clase de utilidad que nos permite manipular varios
flujos de lectura como si fuesen uno solo, concatenándolos uno tras otro. La clase es SequenceInputStream,
derivada de InputStream, y el Listado G es un pequeño programa que
concatena a efectos de lectura los archivos AUTOEXEC.BAT y CONFIG.SYS. El programa es muy
similar al del Listado A, solo que en lugar de tomar la entrada de un archivo, toma
la entrada de un SequenceInputStream que concatena a efectos de lectura dos
archivos. Como con las distintas clases vistas hasta ahora, esta clase define
constructores apropiados: en este caso, el constructor utilizado admite como argumentos
dos flujos de entrada (InputStream) cualesquiera. Hay otro constructor que permite
pasar una lista, en lugar de dos, mediante un argumento del tipo Enumerated.
| import java.io.*; public class entrada_salida3 { static void main( String args[] ) throws IOException { int caracter; int fin_archivo = -1; // Creamos y abrimos los flujos FileInputStream autoexec = new FileInputStream( "c:\\autoexec.bat" ); FileInputStream config = new FileInputStream( "c:\\config.sys" ); InputStream entrada = new SequenceInputStream( autoexec, config ); OutputStream salida = new FileOutputStream( "c:\\copia.aux" ); // Realizamos operaciones de entrada y salida caracter = entrada.read(); while( caracter != fin_archivo ) { salida.write( caracter ); caracter = entrada.read(); } // Cerramos los flujos entrada.close(); salida.close(); } }; |
Listado G: Uso de SequenceInputStream (entrada_salida3.java).
Principal.//Archivos.
//Chat.//Miembros.//Programacion.//Musica.//Herramientas Hacker//101 Tips para W1nDOw$.