Лабораторная работа №3.

Тема: Обработка исключительных ситуаций.

Количество часов: 16 часа.
Рейтинг: 8 балла.

Если ваша программа нарушит семантические правила языка Java, то виртуальная машина Java (JVM) немедленно отреагирует на это выдачей ошибки под названием "исключительная ситуация". Пример такой ситуации - выход за рамки массива. Она может возникнуть при попытке обратиться к элементу за пределами границы массива. Некоторые языки программирования никак не "реагируют" на ошибки программиста и позволяют ошибочным программам выполняться. Но Java не относится к таким языкам. И поэтому программа тщательно проверяет все места, где может возникнуть потенциальная ошибка, а при обнаружении ошибки возбуждаются (throw) исключительные ситуации. Если имеются обработчики таких ситуаций, они перехватывают их (catch) и обрабатывают надлежащим образом.

Программы на языке Java могут самостоятельно возбуждать исключительные ситуации, используя для этого оператор throw. В точке метода, где встречается throw, выполнение метода прерывается и управление передается в тот метод, который вызвал ошибочный. Если исключительная ситуация может быть обработана методом, то вызывается его обработчик. Если же это невозможно, то поток управления передается дальше, и так происходит до того момента, когда исключительная ситуация не будет перехвачена или пока ее не перехватит виртуальная машина Java. В последнем случае выполнение программы прерывается и выводится сообщение об ошибке.

В языке Java каждая исключительная ситуация реализуется как экземпляр класса Throwable или его наследников. Когда в программе нужно отследить возможную исключительную ситуацию, в ней устанавливается обработчик (несколько обработчиков). На практике это оформляется в виде так называемого блока try-catch:

try{
    // Здесь возможно возбуждение
    // исключительной ситуации
} catch (ТипИсключительнойСитуации)
{
    // Здесь производится обработка
// перехваченной исключительной
// ситуации
}
Но не всегда исключительные ситуации - это фатальный сбой. Может, к примеру, оказаться, что программа просто не нашла какой-то файл в каталоге. В этом случае можно перехватить такую ошибку и в обработчик исключительной ситуации вставить оператор вызова диалоговой панели, где пользователь укажет местоположение этого файла. После разрешения этой проблемы программа может быть запущена с того места, где ее выполнение было прервано.

Обычно все методы, в которых может возникнуть исключительная ситуация, описываются особым образом. Например:

static void SomeMethod ()
throws FileNotFoundException {-}
В этом описании оператор throws обозначает, что метод потенциально может создать/вызвать исключительную ситуацию FileNotFoundException, поскольку не найден какой-либо файл. Теперь любой вызов этого метода в программе должен быть обрамлен описанием блока try-catch, иначе компилятор выдаст ошибку и не обработает исходный текст вашей программы. Корректное решение проблемы выглядит примерно следующим образом:

try{
 ..
 static void SomeMethod ();
 ..
} catch (FileNotFoundException exception){
        // Предпринимаем действия
        // по устранению ошибки
}
Конечно, может показаться, что этот способ несколько расточителен и трудоемок. Но зато он гарантирует, что вы обработаете нештатную ситуацию, а не оставите ее "в подарок" пользователю вашей программы.

Существуют и более сложные понятия, например идеология обработки исключений или блоки try-finally. Однако того, что вы прочитали, в большинстве случаев вполне достаточно для повседневной работы.
 
  Тема: InputStream и OutputStream.

Количество часов: 2 часа.
Рейтинг: 2 балла.

Два класса, InputStream и OutputStream, из упаковки java.io служат предками для большинства классов потоков ввода/вывода языка Java, поэтому понимание их структуры и возможностей важно для программиста.

Абстрактный класс InputStream предоставляет начальный интерфейс к потоку ввода данных и частично реализует его. С помощью набора методов, реализуемого классом InputStream, можно читать байты или массивы байтов, узнавать количество доступных для чтения данных, отмечать место в потоке, где в настоящий момент происходит чтение, сбрасывать указатель текущей позиции в потоке и пропускать ненужные байты в потоке.

Открыть поток ввода можно, создав объект класса InputStream. Закрыть его можно двумя способами: дождаться, пока сборщик мусора (garbage collector) Java будет искать в памяти компьютера неиспользуемые классы и закроет ваш поток, или же закрыть его методом close(), как обычно и делается.

Для создания потоков ввода применяется другой класс - OutputStream, который, как и InputStream, является абстрактным. Методы, предоставляемые OutputStream, позволяют записывать байты и массивы байтов в поток вывода. Как и InputStream, поток OutputStream открывается, когда вы его создаете, и закрывается либо сборщиком мусора, либо методом close().

На базе двух упомянутых выше классов InputStream и OutputStream наследуются несколько классов с конкретной спецификой применения, как, например, классы FileInputStream и FileOutputStream для записи и чтения данных из файла.

Тема: FileInputStream и FileOutputStream.

Количество часов: 2 часа.
Рейтинг: 2 балла.

Большинству программистов не просто оперировать сравнительно новым понятием "потоки". Однако два класса - FileInputStream и FileOutputStream - обычно понятны всем, поскольку это ни что иное, как потоки ввода из файла и вывода в него. Мы начнем с рассмотрения FileInputStream. Это довольно универсальный класс, открывающий поток ввода по имени файла. Замечательной особенностью этого класса можно считать возможность создания потока ввода данных по объекту класса File и файловому дескриптору FileDescriptor. Вот, оказывается, какое у этих двух классов применение!

Второй класс, FileOutputStream, служит для записи данных в файл и во многом схож с FileInputStream. Объекты класса FileOutputStream также создаются по имени файла или по объектам File или FileDescriptor. Вот так выглядит простейшая программа на языке Java, копирующая содержимое одного файла в другой файл:

import java.io.*;
class CopyFile
{
public static void main (String[] args)
 {
   try
   {
    File inFile = new
    File("infile.dat");
    File outFile = new
    File("outfile.dat");
    FileInputStream inStream = new
    FileInputStream(inFile);
    FileOutputStream outStream = new
    FileOutputStream(outFile);

    int c;
    while ((c = inStream.read()) != -1)
    { outStream.write(c); }
       inStream.close();
       outStream.close();
   } catch (FileNotFoundException ex) {}
 }
}
В данном случае мы использовали метод создания потоков с промежуточными объектами класса File как пример использования, но ничего не мешает сделать это более простым способом:
    FileInputStream inStream =
    new FileInputStream("infile.dat");
    FileOutputStream outStream =
    new FileOutputStream("outfile.dat");
Результат будет одним и тем же - будут созданы файловые потоки infile.dat и outfile.dat.

Тема: PipedInputStream и PipedOutputStream.

Количество часов: 2 часа.
Рейтинг: 2 балла.

Интересное применение могут найти специализированные потоковые классы. Так, например, два класса, PipedInputStream и PipedOutputStream, введены в иерархию классов Java для создания каналов (pipes), передачи данных от одной программы к другой или от одного потока выполнения (thread) к другому. Каналы широко используются в операционных системах UNIX.

Каналы удобны как средство переопределения потоков, как это делается операторами ">", ">>" или "<" операционной системы для переназначения ввода и вывода данных для программы. В Java создание такого канала сводится к двум строкам исходного текста:

outPipe = new PipedOutputStream ();
inPipe = new PipedInputStream(outPipe);

Тема: SequenceInputStream.

Количество часов: 2 часа.
Рейтинг: 2 балла.

Если необходимо объединить в один поток данные из нескольких потоков, на помощь придет класс SequenceInputStream. Он очень прост в использовании: достаточно передать ему список файлов, выполненных в виде класса, унаследованного от интерфейса Enumeration. Задача значительно упрощается, если требуется объединить всего два потока. Создайте объект класса SequenceInputStream, вызвав другой его конструктор, принимающий два аргумента типа InputStream. Для примера создадим класс списка файлов и передадим его объекту класса SequenceInputStream:

import ileLjava.util.*;
import java.io.*;
class FileList implements Enumeration
{
  String[] fist;
  int count = 0;
   FileList (String[] listOfFiles)
   { this.fileList = listOfFiles;}
    public boolean hasMoreElements()
   {
         if (current < fileList.length)
                 return true;
         else return false;
   }
   public Object nextElement()
   {
         InputStream is = null;
         if (!hasMoreElements())
                 throw new
                 NoSuchElementException
                 ("No more files.");
         else
         {
                 String nextElement =
                 fileList[current];
                 current++;
                 is = new
                 FileInputStream
                 (nextElement);
         }
 return is;
   }
}
Теперь, когда в нашем распоряжении имеется класс-список, на его основе можно создать единый поток данных из нескольких отдельных потоков:
import java.io.*;
class Example
{
   public static void main
   (String[] args)
   {
    ListOfFiles mylist
    = new ListOfFiles(args);
    SequenceInputStreamis
    = new SequenceInputStream(mylist);
   int c;
// Здесь производятся некоторые
    действия над полученным потоком
     s.close();
   }
}

Тема: DataInputStream и DataOutputStream.

Количество часов: 2 часа.
Рейтинг: 2 балла.

DataInputStream и DataOutputStream относятся к так называемым фильтровым классам, то есть классам, задающим фильтры для чтения и записи определенных форматов данных. Фильтровые классы не работают сами по себе, а принимают или отсылают данные простым потокам FileInputStream, FileOutputStream и т. д. Обычное создание потока вывода данных на базе класса DataOutputStream сводится к одной строке:
DataOutStream is =
     new DataOutStream
     ( new FileOutputStream ( "data.dat" ));
После того как поток создан, в него можно выводить форматированные данные. Для этого в арсенале класса DataOutputStream имеется целый набор методов writeXXX() для записи различных данных, где XXX - название типа данных. Вот так выглядит фрагмент кода для вывода в созданный нами поток data.dat:
dos.writeDouble(doubleVar);
dos.writeInt(intVar);
dos.writeChars(StringVar);
dos.close();
Мне кажется, комментарии излишни, поскольку имена методов сами говорят о том, какой тип данных они выводят.

Ну а теперь проверим, как записались наши данные в data.dat, и заодно посмотрим, какие методы для чтения имеются в фильтровом потоке ввода данных DataInputStream:

DataInputStream dis =
     new DataInputStream (
     new FileInputStream ("data.dat" ));
doubleVar = dis.readDouble();
intVar = dis.readInt();
StringVar = dis.readLine();
dis.close();
Как видно из примера, методы чтения readXXX() класса DataInputStream практически полностью соответствуют методам writeXXX() класса DataOutputStream, за исключением методов writeChars и readLine, имеющим по неясным мне причинам различные названия.