Suplemento Nº 8 de New 2 Java (Feb-2002)
Lenguaje Java Básico
Ámbito de Variables
Una declaración de variable le dice al compilador el nombre y el tipo de dato de un trozo específico de memoria y donde se puede utilizar legalmente esta variable. El "donde se puede utilizar legalmente esta variable" es el ámbito de la variable. Cuando una variable entra en ámbito, el sistema de ejecución la crea. Cuando sale del ámbito, el sistema la destruye. Sólo podemos acceder a un variable dentro del ámbito de su declaración.
¿Qué significa que una variable entre en ámbito? Definimos las variables entre una pareja de corchetes, un corchete abierto { y un corchete cerrado }. Cuando el código entra en el conjunto de corchetes más interno alrededor de la declaración de la variable, la variable entra en ámbito. Por ejemplo, en la siguiente declaración de clase, la variable scopeMe entra en ámbito en la línea 1, y la variable scopeYou entra en ámbito en la línea 3:
1: class CheckScope {
2: int scopeMe;
3: public static void main(String args[]) {
4: int scopeYou;
5: }
6: int scopeWe;
7: }
Al igual que scopeMe, la variable scopeWe también entra en ámbito en la ínea 1. Las declaraciones de variabales no tienen porque hacerse al principio de la declaración de la clase.
Esto trae a cuento el punto de los diferentes tipos de variables. Aquí las variables, scopeMe y scopeWe son conocidas como variables miembro. Son parte de la declaración de la clase y son inicializadas cuando se inicializa la clase. Las variables miembro obtienen del compilador un valor por defecto basándose en su tipo de datos:
0 byte, short, int, long
0.0 float, double
false boolean
\u0000 char
null Object
La variable scopeYou es conocida como una variable local. Cuando se llama al método main(), el sistema reserva espacio para la variable scopeYou. Las variables locales no son inicializadas automáticamente y el compilador protestará si intentamos acceder a una variable local no inicializada. Las variables locales están sólo dentro del ámbito del conjunto de corchetes más interno que envuelven la declaración de la variable.
La variable args del método main() de la clase CheckScope es llamada una variable de método. Las variables de método se usan para pasar valores de parámetros a constructores o métodos. El ámbito de las variables de métodos es el método en el que son declaradas. Una vez que un método retorna, no podemos acceder a ningún parámetro (o variable clocal) desde fuera del método.
No mostrado en la definición de la clase CheckScope es el parámetro de la clausula catch de un manejador de excepciones:
try {
// troublesome code
} catch (IOException exception) {
// "exception" is valid
} catch (Exception exception) {
// "exception" is valid
}
aquí, las clausulas try-catch pueden tratar con dos tipos de excepciones que podrían lanzarse desde la sección de código troublesome. Ambas clausulas tratan con un variable exception común como el parámetro del manejador de excepción. Sólo dentro de las respectivas clausulas catch la variable exception está dentro de ámbito. Debido a esta límitación de ambito de los parámetros de los manejadores de excepciones, cada clausula catch puede tener el mismo nombre de variable como su parámetro.
Para demostrar los límites de ámbito, el siguiente código intenta encontrar qué més del año es Junio. Las dos primeras líneas de main() obtienen los meses del año en un array de strings. Luego, el bucle for pasa por el array, antes de imprimir donde se encuentra June.
import java.util.*;
import java.text.*;
public class June {
public static void main(String args[]) {
DateFormatSymbols symbols =
new DateFormatSymbols(Locale.US);
String months[] = symbols.getMonths();
for (int i=0, n=months.length; i < n; i++) {
if (months[i].equals("June")) {
break;
}
}
System.out.println("June month = " + i);
}
}
Aquí se han declarado cinco variables:
- args es un parámetro del método main()
- symbols y months son variables lcoales dentro de main()
- i y n sin variables locales dentro del bucle for.
Como i y n son variables locales dentro del buble for, no podemos acceder a ellas desde fuera del bucle. No son válidas. En este ejemplo particular, sin intentamos compilar la clase, el compilador protestara:
June.java:14: cannot resolve symbol
symbol : variable i
location: class June
System.out.println("June month = " + i);
^
1 error
En otras palabas, la variable i no está en el ámbito nunca más. Para hacer que este programa funcione, debemos mover la declaración de la variable i fuera del bucle for, o usar una variable separada. El último caso se muestra aquí con el ejemplo corregido:
import java.util.*;
import java.text.*;
public class June {
public static void main(String args[]) {
DateFormatSymbols symbols = new DateFormatSymbols(Locale.US);
String months[] = symbols.getMonths();
int j = -1;
for (int i=0, n=months.length; i < n; i++) {
if (months[i].equals("June")) {
j = i;
break;
}
}
System.out.println("June month = " + j);
}
}
Ahora el programa produce el siguiente resultado:
June month = 5
Como los arrays tienen base cero, Junio vuelve como 5. (Enero es 0).
Entender la Librería de Clases Java
Las Clases File y BufferedReader
En el Suplemento Nº 6 aprendimos sobre las clases FileInputStream y FileOutputStream para leer y escribir en objetos stream. Algunas veces se necesita más información, como encontrar si existe un fichero antes de escrbir en él, o acelerar los procesos de lectura y escritura.
La clase File nos permite obtener información sobre un fichero y sus directorios, pero no tiene métodos para leer o escribir ficheros. La clase File tiene métodos útliles para hacer lo siguiente:
Chequear las propiedades de un fichero o directorio:
Crear directorios:
Obtener el path de un fichero o directorio:
Seleccionar las características de un objeto File:
La siguiente aplicación ilustra algunos de estos métodos:
// Import the java.io package to make the File class
// available to you without having to type the package
// name in front of the class name each time.
import java.io.*;
public class TestFileClass
{
public static void main(String[] args)
{
// Creates a reference to a file object.
File aFile = new File("data.txt");
// Creates a file object to use as
// a directory name
File aDir = new File("mydirectory");
// Checks if the file exists and that
// it is a file. If so the name and path
// are printed to the screen.
if(aFile.exists() && aFile.isFile())
System.out.println(aFile.getName() +
" exists and is located at "
+ aFile.getAbsolutePath());
else
System.out.println(aFile + "Does not exist.");
if(aFile.canWrite())
System.out.println(aFile + " can be written to.");
// Sets the file to read only.
aFile.setReadOnly();
System.out.println(aFile + "is now read only.");
// Creates a directory, giving it the
// object name. The if statement tests
// if the directory is successfully
// created.
aDir.mkdir();
if (aDir.isDirectory())
System.out.println(aDir.getName() + " is a directory.");
else
System.out.println("Directory was not created.");
}
}
Una vez que hemos establecido que el fichero existe y que se puede leer, podemos leer los contenidos del fichero un caracter cada vez con la clase FileReader, o mejor todavía, podemos envolver FileReader en BufferedReader. Esto es más eficiente porque crea un buffer de almacenamineto temporal y lee un bloque de caracteres. BufferedReader se usa principalmente para envolver un objeto Reader para su método readLine, que lee una línea de texto y la devuelve como un String. Para hacer estos seguimos estos tres pasos:
- Declaramos una variable string como una referencia para el texto a leer.
- Creamos un objeto BufferedReader que tome un objeto Reader como argumento.
- Llamamos a readLine dentro de un bucle para pasar por todas las líneas del fichero hasta que se devuelva null.
El siguiente ejemplo demuestra este proceso y asume que hemos creado un fichero llamando texttoread.txt:
import java.io.*;
public class TestBufferedReader
{
public static void main(String[] args)
{
String text;
try
{
// Creates a BufferedReader object that takes a
// FileReader object as an argument. The FileReader
// associates an object with the file to be read.
BufferedReader br =
new BufferedReader(new FileReader("texttoread.txt"));
// The while loop calls the readLine method as long as
// there is a line of characters to be read.
while ((text = br.readLine()) != null)
{
System.out.println(text);
}
// Always close a file after opening.
br.close();
}
// Catch and handle the exception in the
// event that the named file cannot be found.
catch (FileNotFoundException fnfe)
{
System.out.println(fnfe);
return;
}
// Catch and handle an exception if there is
// a problem reading the file.
catch (IOException ioe)
{
System.out.println(ioe);
}
}
}
Programa Ejercicio
El siguiente programa se usa para mostrar información sobre un objeto File cuyo nombre es pasado desde la línea de comandos. Si el fichero existe y es un fichero normal, se muestra su contenido. El cierre del fichero sucede dentro de la parte try del bloque try-catch. Si ocurre una excepción después de que el fichero esté abierto, el fichero no se cierra apropiadamente.
Cambia el ámbito de la variable BufferedReader para que el cierre del fichero suceda en una clausula finally del bloque try-catch:
import java.io.*;
import java.util.*;
public class ReadFile {
public static void main(String args[]) {
if (args.length == 0) {
System.err.println("Please provide filename to process");
System.exit(-1);
}
String filename = args[0];
try {
File file = new File(filename);
if (file.exists() && file.isFile()) {
long lastModified = file.lastModified();
System.out.println("Date : " + new Date(lastModified));
System.out.println("Length: " + file.length());
System.out.println("-----");
FileReader fr = new FileReader(file);
BufferedReader br = new BufferedReader(fr);
String line;
while ((line = br.readLine()) != null) {
System.out.println(line);
}
br.close();
}
} catch (IOException e) {
System.err.println("Problem: " + e);
}
}
}
Un poco de Java
Más Atajos de Programación
En el Suplemento Nº 5 aprendimos sobre el uso de bucles for, y en el Suplemento Nº 1 aprendimos sobre los arrays. Aquí, aprederemos como combinar bucles for y arrays para un atajo de programación usado muy frecuentemente. Esta técnica se usa frecuentemene para crear series de componentes GUI.
Cuando aprendas programación GUI, verás un código similar a este:
JLabel nameLabel = new JLabel("Name: ");
JLabel addressLabel = new JLabel("Address: ");
JLabel phoneLabel = new JLabel("phone: ");
JLabel titleLabel = new JLabel("title: ");
JTextField name = new JTextField();
JTextField address = new JTextField();
JTextField phone = new JTextField();
JTextField title = new JTextField();
etc. para declarar e inicializar tantos componentes como tenga el panel o la aplicación. Dichas declaraciones podrían requerir muchas líneas de código y la repetición de los nombres de clases.
En su lugar, podemos usar bucles for para hacer lo mismo, eliminando la necesidad de reteclear o copiar y pegar. El proceso es similar a listar las declaraciones y ejemplarizaciones con la variación de la configuración de arrays para hacer el tabajo en su lugar:
- Configurar las variables para los arrays.
- Configurar las variables para las etiquetas y los campos de texto.
- Inicializar cada objeto pasado pasando el array a un bucle for.
- Añadir los objetos al panel pasando el array a un bucle for.
Por ejemplo, cuando creamos una aplicación con etiquetas y campos de texto, empezamos con un array para el texto:
String names[] = { "Name: ", "Address: ", "Phone: ", "Title: " }
Luego especificamos una variables para referenciar un array de cuatro etiquetas:
JLabel labels[4];
Luego configuramos una variable de referencia para los cuatro campos de texto:
JTextField textfields[4];
En el constructor de la clase, o desde dentro de un método, incializamos todos los objetos label o textField usando un bucle for.
El bucle for se inicializa a 0, chequea la longitud del array labels llamando al método length, luego pasa a través de cada etiqueta, pasando cada etiqueta del array labels en la siguiente línea donde todos son inicializados como un objeto JLabel:
for (int i = 0; i < labels.length; i++)
labels[i] = new JLabel(names[i]);
El bucle de los campos de texto funciona de la misma forma:
for (int i = 0; i < textfields.length; i++)
textfields[i] = new JTextField();
Usamos un bucle for para añadir cada objeto label y textField a un panel, podemos registrar cada uno con un oyente usando el bucle:
for (int i = 0; i < 4; i++)
{
panel.add(labels[i]);
panel.add(textfields[i]);
textfield[i].addActionListener(this);
}
La aplicación de abajo demuestra estos conceptos para compilar y ejecutar. Experimenta con el código, añadiendo más texto al array de strings y creando más etiquetas y campos de texto:
import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
public class ForLoopShortcuts extends JFrame
implements ActionListener
{
private String names[] =
{ "Name: ", "Address: ", "Phone: ", "Title: " };
private JLabel labels[];
private JTextField textfields[];
private int size = 4;
private JPanel panel;
public ForLoopShortcuts()
{
// Calls the JFrame constructor, setting
// the title for the frame.
super("Demo of for Loop Shortcuts");
// Gets the content pane for the frame.
Container c = getContentPane();
// Set up arrays to hold
// four labels and
// four the text fields.
labels = new JLabel[size];
textfields = new JTextField[size];
// The for loop initializes at zero,
// checks the size of the labels
// array, then steps through each.
for (int i = 0; i < labels.length; i++)
// Initializes a new label object,
// assigning the Strings from the String
// array.
labels[i] = new JLabel(names[i]);
// Initializes new text field objects.
for (int i = 0; i < textfields.length; i++)
textfields[i] = new JTextField();
panel = new JPanel();
panel.setLayout(new GridLayout(size,2));
panel.setBackground(Color.white);
// Sets up a loop for the labels and
// textfields array to add to the panels
// and to register each as a listener.
for (int i = 0; i < size; i++)
{
panel.add(labels[i]);
panel.add(textfields[i]);
//panel.textfields[i].addActionListener(this);
}
c.setLayout(new GridLayout(1, 1));
c.add(panel);
//The following line ensures that the application
//closes down when exiting. This method only works
//with J2SE 1.3 to 1.4.
setDefaultCloseOperation(EXIT_ON_CLOSE);
}
public static void main(String[] args)
{
ForLoopShortcuts fls = new ForLoopShortcuts();
fls.setSize(350, 130);
fls.setVisible(true);
}
}
Algunos desarrallodores prefieren declarar e incializar los objetos individualmente porque puede hacer un poco más sencilla la depuración, mientras que otro prefieren ahorrar espacio y teclear menos. La programación usando está técnica es correcta tanto por el estilo como por la conveniencia.
Solución al Programa de Ejercicio
Esta es una posible solución al programa del ejercicio:
:
import java.io.*;
import java.util.*;
public class ReadFile {
public static void main(String args[]) {
BufferedReader br = null;
if (args.length == 0) {
System.err.println("Please provide filename to process");
System.exit(-1);
}
String filename = args[0];
try {
File file = new File(filename);
if (file.exists() && file.isFile()) {
long lastModified = file.lastModified();
System.out.println("Date : " + new Date(lastModified));
System.out.println("Length: " + file.length());
System.out.println("-----");
FileReader fr = new FileReader(file);
br = new BufferedReader(fr);
String line;
while ((line = br.readLine()) != null) {
System.out.println(line);
}
}
} catch (IOException e) {
System.err.println("Problem: " + e);
} finally {
if (br != null) {
try {
br.close();
} catch (IOException ignored) {
}
}
}
}
}
Descargar la Plataforma Java 2
Para la mayoría del desarrollo Java, necesitas las librerías de clases Java, el compilador, las herramientas, y el entorno de ejecución proporcionado por el kit de desarrollo de Java 2, Standard Edition.