Абстрактный класс или интерфейс?
Поможем в ✍️ написании учебной работы
Поможем с курсовой, контрольной, дипломной, рефератом, отчетом по практике, научно-исследовательской и любой другой работой

Интерфейс определяет тип с абстрактным контрактом. Абстрактный класс также определяет тип с абстрактным контрактом. Что именно – интерфейс или абстрактный класс – и когда именно следует использовать в каждом конкретном случае?

Существует два главных различия между интерфейсами и абстрактными классами:

1. Интерфейсы обеспечивают инструментарий множественного наследования, производный класс способен наследовать одновременно несколько интерфейсов. Класс может расширять единственный базовый класс, даже если тот содержит только абстрактные методы.

2. Абстрактный класс частично может быть реализован, он вправе содержать члены, помеченные как protected, и/или static и т.п. Структура интерфейса ограничена объявлениями public-констант и public-методов без какой бы то ни было реализации.

Указанные различия обычно обусловливают выбор средств, наиболее предпочтительных в конкретных обстоятельствах. Если возможность множественного наследования важна, следует обратиться к интерфейсам. Абстрактные классы предлагают реализацию – частичную или даже полную – и поэтому цель может быть достигнута посредством простого наследования вместо необходимости реализации «с нуля». Кроме того, абстрактный класс способен управлять реализацией некоторых методов, обозначая их как final.

Ввод-вывод

Функции ввода-вывода (input/output, или I/O) информации, реализованные в составе стандартного пакета java.io, определены в терминах потоков данных (streams). Потоки данных – это упорядоченные последовательности данных, которым соответствует определённый источник  (для потоков ввода) или получатель (для потоков вывода). Большинство типов потоков данных (например, тех, которые реализуют файловые операции) поддерживает методы определённых базовых интерфейсов и абстрактных классов с некоторыми дополнениями. Мы вначале рассмотрим именно базовые интерфейсы и абстрактные классы, а затем посмотрим примеры практического воплощения абстрактных моделей в конкретных типах потоков данных.

Пакет java.io охватывает определения типов двух основных разновидностей – символьных потоков (character streams) и байтовых потоков (byte streams). Под символьными потоками понимают последовательности 16-битовых символов Unicode, а каждому байту, как всегда, отвечает порция данных длиной 8 бит. Ввод-вывод может быть либо текстовым (text-based), либо бинарным (binary). Функции текстового ввода-вывода способны обращаться с потоками символов, поддающихся восприятию человеком (например, с исходными текстами программ), а средства обработки бинарных данных имеют дело с информацией, хранящейся в двоичном виде (например, с битовыми представлениями графических изображений). Символьные потоки используются в операциях текстового ввода-вывода, а байтовые – при работе с бинарными данными. Потоки, ориентированные на поддержку байтовых данных, вообще говоря, не способны корректно переносить символьную информацию, и некоторые аспекты обработки последовательностей символов в контексте байтовых потоков просто лишены смысла, хотя последние могут с успехом применяться для представления символов на основе устаревших 7-ми и 8-мибитовых протоколов. Байтовые потоки принято называть потоками ввода (input streams) и потоками вывода (output streams), а символьные – потоками чтения (readers) и потоками записи (writers). Почти для каждого потока ввода существует соответствующий поток вывода, а для большинства потоков ввода и вывода определены символьные потоки чтения и записи, обладающие схожими функциональными чертами, и наоборот.

Классы и интерфейсы из состава пакета java.io можно условно разделить на пять обширных групп:

· типы общего назначения, служащие для построения различных типов байтовых и символьных потоков: потоки ввода и вывода, чтения и записи, а также классы, позволяющие выполнять взаимные преобразования между ними;

· классы, определяющие разновидности потоков: фильтрованные потоки (filtered streams), буферизованные потоки (buffered streams), канальные потоки (piped streams), а также специальные подвиды таких потоков, такие как поток чтения строк с сохранением нумерации (line number reader) и лексический анализатор потока чтения (stream tokenizer);

· специализированные потоковые классы и интерфейсы для ввода и вывода значений простых типов и строк;

· классы и интерфейсы для обработки файловых данных в стиле, не зависящем от особенностей платформы;

· классы и интерфейсы, поддерживающие механизм сериализации (serialization) объектов, который позволяет преобразовать объект в байтовый поток и при необходимости восстановить его из данных потока.

Объекты класса IOException используются многими методами ввода-вывода в качестве сигнала о возникновении исключительной ситуации. Некоторые классы, производные от IOException, служат для представления конкретных проблем ввода-вывода, но в большинстве случаев используются объекты IOException со строкой, содержащей описание ошибки.

Байтовые потоки

Существует ряд особенностей, присущих всем байтовым потокам данных. Например, все они поддерживают механизм открытия и закрытия. Поток открывается при создании объекта класса и, оставаясь открытым, позволяет осуществлять операции чтения и записи данных. Поток закрывается при вызове метода close объекта. Закрытие потока служит цели высвобождения системных ресурсов (таких как дескрипторы открытых файлов), которые используются потоком; объект потока должен быть утилизирован сразу по окончании работы с ним. Если поток явно не закрывается, он продолжает существовать и расходовать системные ресурсы. Потоки следует закрывать сразу после завершения их использования.

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

Прежде чем рассматривать детали конкретных типов байтового ввода-вывода, целесообразно изучить особенности соответствующих базовых абстрактных классов: InputStream и OutputStream .

InputStream

В составе абстрактного класса InputStream объявлены методы, обеспечивающие выполнение функций ввода байтовых данных из определённого источника. InputStream является базовым классом по отношению к потокам ввода, определённых в пакете java.io, и обладает методами, перечисленными ниже.

· public abstract int read() throws IOException
Вводит один байт данных и возвращает его в виде целого числа из диапазона от 0 до 255 (но не от-128 до 127); иными словами, байтовое значение трактуется как целое без знака. Если байтов, готовых для ввода, не существует ввиду достижения конца потока, возвращается значение -1. Метод выполняет блокировку до тех пор, пока имеются доступные для ввода байты, не достигнут конец потока либо не выброшено исключение. Метод возвращает именно int, а не byte, поскольку необходимо обеспечить возможность получения как всех корректных значений типа byte, так и флага, свидетельствующего о достижении конца потока, и поэтому в качестве типа возвращаемого значения вместо byte используется более широкий тип int.

· public int read(byte[] buf, int offset, int count) throws IOException
Вводит байты данных и сохраняет их в части массива buf типа byte. Максимальное количество байтов, подлежащих вводу, определяется значением count. Байты заносятся в элементы массива, начиная с buf[offset] и до buf[offset+count-1], если ранее не будет достигнут конец потока, содержимое всех остальных элементов массива buf остаётся неизменным. Метод возвращает количество фактически введённых байтов. Если ввиду достижения конца потока не введён ни один байт, возвращается -1. Если значение count равно нулю, ввод не производится и возвращается 0. Метод выполняет блокировку до тех пор, пока имеются доступные для ввода байты, не достигнут конец потока либо не выброшено исключение. Если первый байт не может быть введён по причине, не связанной с достижением конца потока (например, из-за того, что поток уже закрыт), выбрасывается исключение типа IOException. Как только успешно введён хотя бы один байт, любая ошибка, возникающая при попытке ввода последующих данных, не приводит к выбрасыванию исключения, а трактуется как событие получения признака конца потока – метод завершает выполнение нормальным образом и возвращает число байтов, введённых до момента возникновения ошибки.

· public int read(byte[] buf) throws IOException
Метод аналогичен предыдущему при условии read(buf, 0, buf.length).

· public long skip(long count) throws IOException
Пропускает, самое большее, count байтов либо часть последовательности байтов до конца потока. Возвращает количество фактически пропущенных байтов. Если значение count отрицательно, операция не выполняется.

· public int available() throws IOException
Возвращает количество байтов, которые могут быть введены или пропущены, не вызывая блокировки. Реализация метода, предлагаемая по умолчанию, возвращает значение 0.

· public void close() throws IOException
Закрывает ранее открытый поток ввода. Метод используется для высвобождения ресурсов, связанных с потоком (таких как дескрипторы открытых файлов). Любые попытки обращения к закрытому потоку приводят к выбрасыванию исключения типа IOException, но повторное закрытие потока эффектов не вызывает. В реализации метода, предлагаемой по умолчанию, никакие действия не предусмотрены.

При реализации InputStream требуется, чтобы производный класс обеспечил практическое воплощение варианта метода read, осуществляющего ввод единственного байта, поскольку остальные версии read основываются на первой. Во многих случаях, однако, производительность операций может быть улучшена за счёт переопределения и других методов. Исходные версии методов available и close нуждаются в переопределении в соответствии с потребностями конкретных типов потоков.

Следующая программа (пример 35) демонстрирует приёмы использования потоков ввода для подсчёта общего количества байтов в указанном файле либо в стандартном потоке System . in , если имя файла не задано.

Пример 35.

import java.io.*;

class CountBytes {

public static void main(String[] args) throws IOException {

     InputStream in;

     if (args.length == 0)

          in = System.in;

     else

          in = new FileInputStream(args[0]);

     int total = 0;

     while (in.read() != -1)

          total++;

    System.out.println(total + " байтов");

}

}

Программа принимает имя файла в виде параметра командной строки. Переменная in представляет байтовый поток ввода. Если имя файла не задано, используется стандартный поток ввода, System . in в противном случае создаётся объект типа FileInputStream , расширяющего класс InputStream . В цикле while подсчитывается общее количество байтов в файле, а заетем на экран выводится результат. Так выглядит итог работы программы, которой в качестве параметра передано имя файла с её же исходным текстом:

«350 байтов»

Можно было бы попытаться вычислить значение total, воспользовавшись методом available, но для многих типов потоков результат, полученный таким образом, окажется, вообще говоря, неверным. Метод available возвращает количество байтов, которые можно ввести без блокировки. Если в качестве источника данных используется файл, available, вероятно, вернёт как можно было предвидеть, число байтов, образующих содержимое файла в целом. Если поток System.in связан, скажем, c клавиатурой, результатом может оказаться даже нуль: когда входных данных, ожидающих обработки, нет, очередной вызов read вызовет блокировку.







OutputStream

Абстрактный класс OutputStream во многом аналогичен классу InputStream; он предлагает модель вывода байтовых данных в объект - получатель. Методы класса OutputStream перечислены ниже:

· public abstract void write(int b) throws IOException
Выводит значение b в виде байта. В качестве параметра передаётся именно int, поскольку значение часто является результатом выполнения арифметических операций над аргументами byte. Выражение, операнды которого относятся к типу byte, обладает типом int, поэтому объявление параметра в виде int означает возможность избежать необходимости использования операций явного преобразования результатов вычисления выражения к типу byte. Следует заметить, однако, что выводится только 8 младших битов результат типа int. Метод выполняет блокировку до тех пор, пока байт не выведен.

· public void write(byte[] buf, int offset, int count) throws IOException
Выводит count байтов массива buf, начиная с элемента buf[offset]. Метод выполняет блокировку до тех пор, пока байты не выведены.

· public void write(byte[] buf) throws IOException
Метод аналогичен предыдущему при условии write(buf, 0, buf.length).

· public void flush() throws IOException
Осуществляет сброс (flush) потока. Если поток сохраняет в промежуточном буфере некоторое число байтов, преданных методами write различных версий, flush провоцирует выполнение операции непосредственного вывода данных в объект-получатель. Затем, если получателем является другой поток, тот в свою очередь также сбрасывается. Таким образом, единственный вызов flush приводит к сбросу всех буферов в цепочке взаимосвязанных потоков. Если поток не относится к категории буферизированных, реализация метода, предлагаемая по умолчанию, не предусматривает выполнения каких бы то ни было действий.

· public void close() throws IOException
Закрывает ранее открытый поток вывода. Метод используется для высвобождения ресурсов, связанных с потоком (таких как дескрипторы открытых файлов). Любые попытки обращения к закрытому потоку приводят к выбрасыванию исключения типа IOException, но повторное закрытие потока эффектов не вызывает. В реализации метода, предлагаемой по умолчанию, никакие действия не предусмотрены.

При реализации OutputStream требуется, чтобы производный класс обеспечил практическое воплощение варианта метода write, осуществляющего вывод единственного байта, поскольку остальные версии write основываются на первой. Во многих случаях, однако, производительность операций может быть улучшена за счёт переопределения и других методов. Исходные версии методов flush и close нуждаются в переопределении в соответствии с потребностями конкретных типов потоков - в частности, перед закрытием буферизованного потока разумно выполнять его сброс.






Символьные потоки

Абстрактными классами, обеспечивающими чтение и запись символьных потоков, являются Reader и Writer. В каждом из них предусмотрены методы, схожие с «собратьями» из состава классов, представляющих байтовые потоки (соответственно, InputStream и OutputStream). Например, класс OutputStream обладает методами для вывода данных из массивов типа byte , а Writer предлагает аналогичные методы для записи значений из массивов типа char . Классы символьных потоков появились в составе Java после реализации байтовых потоков и имели целью обеспечение полной поддержки таблиц символов Unicode. Как и в случае байтовых потоков, по окончании использования символьного потока его следует закрывать, чтобы высвободить используемые им системные ресурсы.

Reader

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

· public int read() throws IOException
Считывает один символ данных и возвращает его в виде целого числа из диапазона от 0 до 65535. Если символов, готовых для чтения, не существует ввиду достижения конца потока, возвращается значение -1. Метод выполняет блокировку до тех пор, пока имеются доступные для чтения символы, не достигнут конец потока либо не выброшено исключение.

· public abstract int read(char[] buf, int offset, int count)
throws IOException
Считывает символы и сохраняет их в части массива buf типа char. Максимальное количество символов, подлежащих чтению, определяется значением count. Символы заносятся в элементы массива, начиная с buf[offset] и вплоть до buf[offset+count-1], если ранее не будет достигнут конец потока, - содержимое всех остальных элементов массива buf остаётся неизменным. Метод возвращает количество фактически считанных символов. Если ввиду достижения конца потока не считан ни один символ, возвращается -1. Если значение count равно нулю, чтение не производится и возвращается 0. Метод выполняет блокировку до тех пор, пока имеются доступные для чтения символы, не достигнут конец потока либо не выброшено исключение. Если первый символ не может быть считан по причине, не связанной с достижением конца потока (например, из-за того, что поток уже закрыт), выбрасывается исключение типа IOException. Как только успешно прочитан хотя бы один символ, любая ошибка, возникающая при попытке чтения последующих данных, не приводит к выбрасыванию исключения, а трактуется как событие получения признака конца потока, метод завершает выполнение нормальным образом и возвращает число символов, считанных до момента возникновения ошибки.

· public int read(char[] buf) throws IOException
Метод аналогичен предыдущему при условии read(buf, 0, buf.length).

· public long skip(long count) throws IOException
Пропускает, самое большее, count символов либо часть последовательности символов до конца потока. Возвращает количество фактически пропущенных символов. Значение count не должно быть отрицательным.

· public boolean ready() throws IOException
Возвращает true, если поток готов для чтения данных, т.е. в нём существует хотя бы один доступный символ. Заметим, что результат, равный false, отнюдь не свидетельствует о том, что очередной вызов read приведёт к возникновению блокировки, поскольку в промежутке между обращениями к ready и read в поток может поступить очередная порция данных.

· public abstract void close() throws IOException
Закрывает ранее открытый поток чтения. Метод используется для высвобождения ресурсов, связанных с потоком (таких как дескрипторы открытых файлов). Любые попытки обращения к закрытому потоку приводят к выбрасыванию исключения типа IOException, но повторное закрытие потока эффектов не вызывает.

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

Между классами Reader и InputStream существует ряд различий. В Reader базовым абстрактным методом чтения является вариант метода read, осуществляющий чтение в массив типа char, а остальные версии основываются на базовой. В классе InputStream в качестве основного объявлен метод read, выполняющий ввод одного байта. Классы, производные от Reader, обязаны предоставить реализацию абстрактного метода close: в отличие от наследования пустой реализации; многим классам потоков, как правило, необходимо «знать», был ли закрыт поток или нет, так что метод close обычно подлежит переопределению. Наконец, в классе InputStream предусмотрен метод available, позволяющий выяснить, какой объём данных готов для ввода, а класс Reader предоставляет метод ready, свидетельствующий о том, существуют ли такие данные или нет.

В качестве примера приведём программу, выполняющую подсчёт общего количества символов и числа знаков пробела в символьном потоке чтения (пример 36).

Пример 36.

import java.io.*;

class CountSpace {

public static void main(String[] args) throws IOException {

     Reader in;

     if (args.length == 0)

          in =new InputStreamReader(System.in);

     else

          in = new FileReader(args[0]);

     int ch;

     int total;

       int spaces = 0;

    for (total = 0; (ch = in.read()) != -1; total++) {

        if (Character.isWhitespace((char) ch))

            spaces++;

    }

    System.out.println(total + " символов , " + spaces + " пробелов ");

}

}

Программа принимает имя файла в виде параметра командной строки. Переменная in представляет символьный поток чтения. Если имя файла не задано, используется стандартный поток ввода, System . in , после «вложения» его в объект типа InputstreamReader , выполняющий преобразование байтового потока ввода в символьный поток чтения; в противном случае создаётся объект типа FileReader , расширяющий класс Reader . В цикле for подсчитывается общее количество символов в файле; кроме того, с помощью метода isWhitespace класса Character выявляются символы пробела и вычисляется их количество, а затем на экран выводится результат. Так выглядит итог программы, которой в качестве параметра передано имя файла с её же исходным тектом:

477 символов , 130 пробелов








Writer

Абстрактный класс Writer обеспечивает поддержку символьного потока записи аналогично тому, как это делает OutputStream, реализующий модель байтового потока вывода, и многие методы Writer схожи с теми, которые объявлены в составе OutputStream; помимо этого, в классе Writer предусмотрены некоторые другие полезные версии метода write.

· public void write(int ch) throws IOException
Записывает ch в виде символа. Символ передаётся как значение int, но записывается только 16 битов последнего. Метод выполняет блокировку до тех пор, пока символ не записан.

· public abstract void write(char[] buf, int offset, int count) throws IOException
Записывает count символов массива buf, начиная с элемента buf[offset]. Метод выполняет блокировку до тех пор, пока символы не записаны.

· public void write(char[] buf) throws IOException
Метод аналогичен предыдущему при условии write(buf, 0, buf.length).

· public void write(String str, int offset, int count) throws IOException
Записывает count символов строки str, начиная с символа str.charAt(offset).

· public void write(String str) throws IOException
Метод аналогичен предыдущему при условии write(str, 0, str.length()).

· public abstract void flush() throws IOException
Осуществляет сброс (flush) потока. Если поток сохраняет в промежуточном буфере некоторое число символов, преданных методами write различных версий, flush провоцирует выполнение операции непосредственной записи данных в объект-получатель. Затем, если получателем является другой поток, тот в свою очередь также сбрасывается. Таким образом, единственный вызов flush приводит к сбросу всех буферов в цепочке взаимосвязанных потоков. Если поток не относится к категории буферизированных, никакие действия не выполняются.

· public abstract void close() throws IOException
Закрывает ранее открытый поток записи, выполняя при необходимости его сброс. Метод используется для высвобождения ресурсов, связанных с потоком (таких как дескрипторы открытых файлов). Любые попытки обращения к закрытому потоку приводят к выбрасыванию исключения типа IOException, но повторное закрытие потока эффектов не вызывает.

Классы, производные от Writer, обязаны обеспечить реализацию варианта метода write, связанного с записью символов из части массива, а также методов close и flush. Все остальные методы Writer основаны на трёх методах, названных выше. Это отличает Writer от OutputStream, в котором в качестве базового метода вывода предусмотрен вариант write, осуществляющий вывод одного байта, а для методов close и flush предложены реализации по умолчанию. Как и в случае класса Reader, производительность операций может быть улучшена за счёт переопределения в производных классах и других методов.








Дата: 2019-02-19, просмотров: 202.