Вопросы по Java на собеседовании (4)

1. Потоки ввода/вывода
2. Байтовый поток ввода InputStream
3. Байтовый поток вывода OutputStream
4. Символьный поток чтения Reader
5. Символьный поток записи Writer
6. Преобразование байтовых потоков в символьные и обратно
7. Класс File для работы с файловой системой
8. Символ-разделитель в определении пути файла
9. Фильтрация списка файлов, FileFilter
10. Класс чтения содержимого файла FileInputStream
11. Класс записи содержимого в файл FileOutputStream
12. Стратегии чтения XML документа : DOM и SAX
13. Классы создания XML-объекта Document
14. Generic и его реализации
15. Использование wildcard
16. Bounded wildcard
17. Wildcard capture
18. Wildcard capture helper

Вопросы и ответы для собеседование по Java, Содержание.
Вопросы и ответы для собеседование по Java, часть 1.
Вопросы и ответы для собеседование по Java, часть 2.
Вопросы и ответы для собеседование по Java, часть 3.
Вопросы и ответы для собеседование по Java, часть 5.
Вопросы и ответы для собеседование по Java, часть 6.

1. Потоки ввода/вывода

Поток ввода/вывода - это абстрактное понятие источника или приёмника данных, которые способны передавать информацию. Имеется два вида потоков ввода/вывода : байтовые и символьные. К байтовым потокам относятся java.io.InputStream, java.io.OutputStream. Символьные потоки – java.io.Reader, java.io.Writer. Данные потоки включены в пакет java.io.

Базовый класс InputStream и его наследники получают данные из различных источников : массив байтов, строки (String), файлы, каналы pipe, у которых одна из сторон является входом, а вторая сторона играет роль выхода и т.д.

Абстрактный класс OutputStream определяет байтовый поток вывода. К этой категории относятся классы, определяющие выходные данные, помещаемые в массив байтов, в файл или канал. Напрямую в объект String вывести данные из OutputStream нельзя, можно создать текстовую строку (String) из массива байтов.

Символьные потоки включают два основных абстрактных класса Reader и Writer, управляющие потоками Unicode символов. Класс Reader определяет символьный поток ввода. Класс Writer определяет символьный поток вывода. При возникновении ошибки методы данных классов вызывают исключение IOException.

Потоки ввода данных включают методы read () для чтения отдельных байтов или массива байтов. Потоки вывода данных включают методы write () для записи одиночных байтов или массива байтов.

Классы-надстройки потоков ввода/вывода

К классам-надстройкам относятся классы, наследующие свойства базовых классов и расширяющие их свойства. В качестве примеров можно привести классы BufferedOutputStream, BufferedInputStrem, BufferedWriter, BufferedReader, которые буферезируют поток, повышая, таким образом, производительность.

2. Байтовый поток ввода InputStream

Основные методы класса InputStream

МетодОписание
int read () чтение во входном потоке очередного доступного символа в виде целого
int read (byte b[]) чтение во входном потоке b.length байтов в массив b; возвращает количество прочитанных из потока байтов
int read (byte b[], int off, int len) чтение len байтов в массиве b, начиная со смещения off; возвращает количество прочитанных из потока байтов
int available () получение количества доступных для чтения байтов
void close () закрытие потока ввода; последующие попытки чтения из потока вызовут IOException

Полный список методов класса InputStream представлен здесь.

Классы-надстройки базового байтового потока ввода :

ByteArrayInputStream Использует байтовый массив в памяти в качестве источника данных для входного потока. Класс содержит конструктор, включающий в качестве параметров массив байтов для чтения, смещение относительно начала массива и количество считываемых символов. Пример.
FileInputStream Основной класс для работы с файлами. Конструктор класса получает путь к файлу. Пример.
FilterInputStream Абстрактный класс, предоставляющий интерфейс для классов-надстроек BufferedInputStream и DataInputStream.
ObjectInputStream Входной поток для сериализованных данных, т.е. информация в потоке представляет набор сериализованных объектов.
PipedInputStream Специальный класс, используемый для связи отдельных программ (потоков) друг с другом внутри одной JVM. Пример.

Классы BufferedInputStream и DataInputStream, наследуют свойства FilterInputStream. BufferedInputStream используется для организации более эффективного "буферизованного" ввода данных. DataInputStream применяется для чтения байтовых данных (не строк).

3. Байтовый поток вывода OutputStream

Класс OutputStream – это абстрактный класс, определяющий байтовый поток вывода. К данной категории относятся классы, определяющие направление вывода данные: массив байтов, файл или канал.

Классы-надстройки байтовых потоков вывода :

ByteArrayOutputStream Поток вывода в массив байтов. Пример.
FileOutputStream Поток вывода массива байтов в файл. Пример.
FilterOutputStream Абстрактный класс, предоставляющий интерфейс для классов-надстроек BufferedOutputStream, DataOutputStream, PrintStream.
ObjectOutputStream Класс используется для сериализации объектов при отправлении в поток. Пример.
PipedOutputStream Класс используется для установления связи между двумя каналами. Пример.

Основные методы класса OutputStream

МетодОписание
void write (int b) запись байта в выходной поток
void write (byte b[]) запись в выходной поток массива байтов
void write (byte b[], int off, int len) запись в поток len байтов массива, начиная с элемента b[off]
void flush () завершение операции вывода и очистка выходных буферов
void close () закрытие выходного потока; последующие попытки записи в поток вызовут IOException

4. Символьный поток чтения Reader

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

Классы-надстройки символьных потоков чтения :

BufferedReader Буферизированный входной символьный поток. Пример.
CharArrayReader Входной поток чтения символьного массива.
StringReader Входной поток чтения из строки.
FileReader Входной поток чтения содержимого файла.
FilterReader Входной поток с фильтрацией.
InputStreamReader Входной поток преобразования байтов в символы.
LineNumberReader Входной поток, подсчитывающий строки.
PipedReader Входной поток чтения данных из канала.

5. Символьный поток записи Writer

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

Классы-надстройки символьных потоков записи :

BufferedWriter Буферизированный выходной символьный поток. Пример.
CharArrayWriter Выходной поток записи в символьный массив.
StringWriter Выходной поток записи в строку.
FileWriter Выходной поток записи в файл.
FilterWriter Выходной поток записи с фильтрацией.
OutputStreamWriter Выходной поток преобразования байтов в символы.
LineNumberReader Выходной поток с подсчетом строк.
PipedWriter Выходной поток записи в канал.

6. Преобразование байтовых потоков в символьные и обратно

OutputStreamWriter является мостом между классом OutputStream и классом Writer. Записанные в поток символы OutputStreamWriter преобразовывает в байты.

OutputStream os = new FileOutputStream("c:\\output.txt");
Writer       wr = new OutputStreamWriter(os, "UTF-8");
 
wr.write("Hello World");
wr.close();

InputStreamReader является мостом между классом InputStream и классом Reader. При помощи методов класса Reader прочитанные байты из потока InputStream преобразуются в символы.

InputStream is  = new FileInputStream("c:\\input.txt");
Reader      isr = new InputStreamReader(is, "UTF-8");
 
int data = isr.read();
while(data != -1){
    char theChar = (char) data;
    data = isr.read();
}
 
isr.close();

7. Класс File для работы с файловой системой

Для управления информацией о файлах и каталогах используется класс java.io.File. На уровне операционной системы файлы и каталоги имеют существенные отличия, но в Java они описываются одним классом File. В Java каталог трактуется как обычный файл, но с дополнительным свойством — списком имен файлов, который можно просмотреть с помощью метода list ().

Класс File позволяет получить следующую информацию о файле: права доступа, время и дата создания, путь к каталогу. Также он используется для навигации по иерархиям подкаталогов.

Для создания объектов File можно использовать один из следующих конструкторов :

МетодПараметры
File (File dir, String name) файл name в каталоге dir
File (String path) path — полный путь к файлу
File (String dirPath, Sring name) dirPath — путь к директории, name — наименование файла
File (URI uri) uri — объект описания файла

Примеры создания File :

// Объект каталога File
File dir = new File("с:/dir");
// Объекты файлов
File file1 = new File("c:/dir", "Hello1.txt");
File file2 = new File(dir, "Hello2.txt");

Свойства и методы класса File представлены здесь.

8. Символ-разделитель в определении пути файла

Получить символ-разделитель, используемый в описании пути к файлу/директории можно с использованием статического поля File.separator типа String, либо File.separatorChar типа char. Символы-разделители в Windows ("\\") и Linux ("/") отличаются направлением наклона. Автор данных строк, как правило, использует linux'овый символ-разделитель '/' при работе в ОС Windows; JVM отрабатывает правильно.

9. Фильтрация списка файлов, FileFilter

Класс File включает метод listFiles (FileFilter filter) , позволяющий прочитать список только определенных файлов. Параметр метода FileFilter предназначен для определения условия выбора файлов. FileFilter — являтся интерфейсом, который имеет всего один метод boolean accept (File pathname), возвращающий true, если файл удовлетворяет определенным условиям, и false в противном случае.

Подробнее об описании интерфейса FileFilter и пример использования файловой фильтрации можно прочитать здесь.

10. Класс FileInputStream для чтения содержимого файла

Класс FileInputStream, являющийся наследником InputStream, предназначен для работы с двоичными файлами. Конструктор класса «FileInputStream (String fileName) throws FileNotFoundException» в качестве параметра получает путь к файлу. Если файл не может быть открыт, то генерируется исключение FileNotFoundException.

Пример использования FileInputStream для чтения данных из файла и вывода содержимого в консоль :

FileInputStream fis;
try { 
    fis = new FileInputStream("C:\\test_dir\\test.txt");
    System.out.println("Размер файла: " + 
                        fis.available() + " байт(а)");
    int i = -1;
    while(( i = fis.read()) != -1){
        System.out.print((char)i);
    }
    fis.close();
} catch(IOException e){
    System.err.println(e.getMessage());
}

Пример можно немного изменить и сохранить данные в массив байтов :

byte[] buffer = new byte[fis.available()];
// чтение файла в буфер
fis.read (buffer, 0, fis.available());
 
System.out.println ("Содержимое файла :");
for(int i = 0; i < buffer.length; i++){
    System.out.print((char)buffer[i]);
}

Дополнительно о FileInputStream можно прочитать здесь.

11. Класс записи содержимого в файл FileOutputStream

Класс FileOutputStream, являющийся наследником OutputStream, предназначен для работы с двоичными файлами. Конструктор класса «FileOutputStream (String fileName) throws IOException» в качестве параметра получает путь к файлу. Если файл невозможно будет создать, то генерируется исключение IOException.

String text = "Hello world!"; // строка для записи
try {
    FileOutputStream fos = new FileOutputStream("C:\\test.txt");
    // перевод строки в байты
    byte[] buffer = text.getBytes();
    fos.write(buffer, 0, buffer.length);
} catch(IOException e){
    System.out.println(e.getMessage());
}

Дополнительно о FileOutputStream можно прочитать здесь.

12. Стратегии чтения XML документа : DOM и SAX

Java использует 2 стратегии обработки XML документов: DOM (Document Object Model) и SAX (Simple API for XML). Отличие двух разных подходов к чтению XML документа связано с тем, что в первом случае (DOM) размер документа определен и он сразу же может быть разложен (распарсен). Во втором случае, например, при получении XML документа от WEB-сервиса, размер документа неизвестен, и он анализируется (раскладывается) в процессе поступления потока информации.

То есть, при использовании стратегии DOM необходимо весь документ «заглотить» и проанализировать, а стратегия использования SAX основывается только на анализе содержимого XML-документа, не требующая дополнительной памяти.

13. Классы создания XML-объекта Document

Для чтения или создания объекта Document используется 3 класса :

  • DocumentBuilderFactory
  • DocumentBuilder
  • Document

DocumentBuilderFactor содержит статический метод newInstance(), создающий экземпляр класса. Document создается одним из методов объекта DocumentBuilder : parse (File) или newDocument(). Пример :

DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
DocumentBuilder        db  = dbf.newDocumentBuilder();

// чтение из файла
Document  doc1 = db.parse(new File("data.xml"));

// новый документ
Document  doc2 = db.newDocument();

Дополнительно о методах получения информации об узлах объекта Document и их атрибутах можно прочитать здесь.

14. Generic и его реализации

Generic - это параметризованный тип, используемый для объявления классов, интерфейсов и методов, где тип данных указан в виде параметра. 2 примера использования коллекции :

// вариант 1, приведение типа
List intList = new LinkedList(); 
intList.add(new Integer(23));
Integer x = (Integer) intList.iterator().next();

// вариант 2, использование generic
List<Integer> intList = new LinkedList<Integer>();
intList.add(new Integer(0)); 
Integer x = intList.iterator().next(); 

В первом варианте используется приведение типа (java casting), во втором – generic, не требующий java casting.

Для определения параметризованного типа используются угловые скобки и символ (заполнитель), вместо которого будет подставлен реальный тип. Угловые скобки указывают, что параметр может быть обобщён. Сам класс (интерфейс, метод) при этом называется обобщённым или параметризованным. Пример определения и использования параметризированного класса :

class GenericClass<T>
{ 
    private T value; 
 
    public GenericClass(T value) { 
        this.value = value; 
    } 
    public T getValue() { 
        return value; 
    } 
    public String toString() { 
        return "{" + value + "}"; 
    } 
}
...
GenericClass<Integer> value1;
GenericClass<String>  value2;

value1 = new GenericClass<Integer>(new Integer(10)); 
value2 = new GenericClass<String> ("Hello world"); 

System.out.println(value1); 
System.out.println(value2); 

Integer intValue1 = value1.getValue(); 
         
// Здесь возникает ошибка несоответствия типа 
Integer intValue2 = value2.getValue(); 
		

Generic работает только с объектом; нельзя использовать в качестве параметра элементарные типы int или char. Внутри generic'a класса информация о типе параметра не хранится. Это называется стиранием типов. На стадии компиляции происходит приведение объекта класса к типу, который был указан при объявлении.

15. Использование wildcard

Обобщение wildcard, представляющий символ "?", используется в тех случаях, когда необходимо в качестве параметра указать соответствие любому типу. Так, например, Collection<Object> не является полностью родительской коллекцией всех остальных коллекций; то есть, Collection<Object> имеет ограничения. Но, если, использовать Collection<?>, то ограничений в использовании нет.

void dump(Collection<?> c) {
  for (Iterator<?> i = c.iterator(); i.hasNext();) {
     Object o = i.next();
     System.out.println(o);
  }
}
. . .
List<Object>  obj;
List<Integer> lst;
. . .
dump(obj);
dump(lst);

В примере метод dump может быть вызван с любым типом коллекции.

16. Bounded wildcard

Bounded wildcard позволяет использовать наследование объектов при определении обобщенных типов.

Допустим, фигуры Rectangle и Cycle наследуют свойство базового класса Shape. В этом случае можно указывать тип «? extends Shape», включающий класс Shape и его наследников, либо «? super Rectangle», включающий класс Rectangle и всех его предков вплоть до Object. Пример :

void draw(List<? extends Shape> c) {
   for (Iterator<Shape> i = c.iterator(); i.hasNext(); ) {
       Shape s = i.next();
       s.draw();
   }
}
... 
List<Shape>  shapes;
List<Circle> cycles;
...

draw(shapes);
draw(cycles);

Процедуру draw можно можно вызывать с параметром Shape и его наследником.

17. Wildcard capture

Рассмотрим представленный в листинге метод :

public void rebox(List <?> box) {
    box.add(box.get(0));
}

Теоретически метод должен быть работающим : из коллекции извлекается элемент и повторно добавляется в эту же коллекцию. Таким образом, тип добавляемого объекта соответствует типу объектов коллекции. Однако среда разработки IDE (Eclipse), равно, как и компилятор, выдают сообщение об ошибке, связанное с несовместимостью типов :


The method add(capture#1-of ?) in the type 
    List<capture#1-of ?> is not applicable 
    for the arguments (capture#2-of ?)
 

Когда компилятор встречает переменную с wildcard'ом в ее типе, он знает, что здесь должен быть некий объект T. Тип объекта T ему неизвестен, и он может создать заглушку для данного типа, которая будет ссылаться на подставляемый T. Такое место вставки называется «capture» конкретного wildcard'а. В рассматриваемом случае компилятор назначил имя «capture#1-of ?» знаку wildcard в типе List сигнатуры метода. Но, каждое вхождение wildcard'а в переменные метода дает разные «capture». Поэтому, для метода get(0) была создана другая capture, отличающаяся от объявленного тип. Разнотипные «capture» блокируют компиляцию кода и выдают соответствующую ошибку.

18. Wildcard capture helper

В п.17 было сказано, что компилятор не пропустит метод rebox (см. листинг) и выдаст ошибку несовмещения типов. Данную проблему можно решить, используя механизм «capture helper», позволяющий вспомогательным методом предопределить тип wildcard'a. Рассмотрим следующий пример :

public void rebox(List <?> box) {
    reboxHelper(box);
}

private<V> void reboxHelper(List <V> box) {
    box.add(box.get(0));
}

Вспомогательный метод reboxHelper() использует дополнительный generic (<V>), помещаемый перед типом возвращаемого значения. Если его не указать, то IDE (Eclipse) и копмилятор сообщат об ошибке :


 The method reboxHelper(List<V>) from the type
   'ClassName'  refers to the missing type V
 

Generic-метод reboxHelper не задействует параметр типа, а позволяет компилятору определить имя параметра типа переменной box. Таким образом, использование helper'а позволяет обойти ограничения компилятора на wildcard. Когда rebox() вызывает reboxHelper(), то ему уже известно, что это действие безопасно, поскольку его собственный параметр box должен иметь тип List<V>. Теперь выражение box.get(0) в reboxHelper() имеет тип не Object, а V, и можно передать параметр V в box<V>.add().

Вопросы и ответы для собеседование по Java, Содержание.
Вопросы и ответы для собеседование по Java, часть 1.
Вопросы и ответы для собеседование по Java, часть 2.
Вопросы и ответы для собеседование по Java, часть 3.
Вопросы и ответы для собеседование по Java, часть 5.
Вопросы и ответы для собеседование по Java, часть 6.

  Рейтинг@Mail.ru