Главная страница

Ответы на вопросы по ревью 4. Java io. Ключевым понятием здесь является понятие потока


Скачать 1.93 Mb.
НазваниеJava io. Ключевым понятием здесь является понятие потока
Дата03.07.2022
Размер1.93 Mb.
Формат файлаdoc
Имя файлаОтветы на вопросы по ревью 4.doc
ТипДокументы
#623608
страница39 из 39
1   ...   31   32   33   34   35   36   37   38   39

Токенизация(Tokenizing) ввода


Tokenizing - это процесс разбивания последовательности символов на последовательность значащих элементов (“tokens”), которые являются кусочками текста, разделенных чем-либо по вашему выбору. Например, ваши значащие элементы могут быть словами, разделенными пробелом и пунктуацией. Есть два класса, обеспечиваемых стандартной библиотекой Java, которые могут использоваться для токенизации: StreamTokenizer и StringTokenizer.

StreamTokenizer


Хотя StreamTokenizer не наследуется от InputStream или OutputStream, он работает только с объектами InputStream, так что он по праву принадлежит библиотеке ввода/вывода.

Рассмотрим программу, подсчитывающую встречающихся слов в текстовом файле:

//: c11:WordCount.java

// Подсчет слов в файле, выводит

// результат в отсортированном порядке.

import java.io.*;

import java.util.*;
class Counter {

private int i = 1;

int read() { return i; }

void increment() { i++; }

}
public class WordCount {

private FileReader file;

private StreamTokenizer st;

// TreeMap хранит ключи в отсортированном порядке:

private TreeMap counts = new TreeMap();

WordCount(String filename)

throws FileNotFoundException {

try {

file = new FileReader(filename);

st = new StreamTokenizer(

new BufferedReader(file));

st.ordinaryChar('.');

st.ordinaryChar('-');

} catch(FileNotFoundException e) {

System.err.println(

"Could not open " + filename);

throw e;

}

}

void cleanup() {

try {

file.close();

} catch(IOException e) {

System.err.println(

"file.close() unsuccessful");

}

}

void countWords() {

try {

while(st.nextToken() !=

StreamTokenizer.TT_EOF) {

String s;

switch(st.ttype) {

case StreamTokenizer.TT_EOL:

s = new String("EOL");

break;

case StreamTokenizer.TT_NUMBER:

s = Double.toString(st.nval);

break;

case StreamTokenizer.TT_WORD:

s = st.sval; // Уже String

break;

default: // единственный символ в ttype

s = String.valueOf((char)st.ttype);

}

if(counts.containsKey(s))

((Counter)counts.get(s)).increment();

else

counts.put(s, new Counter());

}

} catch(IOException e) {

System.err.println(

"st.nextToken() unsuccessful");

}

}

Collection values() {

return counts.values();

}

Set keySet() { return counts.keySet(); }

Counter getCounter(String s) {

return (Counter)counts.get(s);

}

public static void main(String[] args)

throws FileNotFoundException {

WordCount wc =

new WordCount(args[0]);

wc.countWords();

Iterator keys = wc.keySet().iterator();

while(keys.hasNext()) {

String key = (String)keys.next();

System.out.println(key + ": "

+ wc.getCounter(key).read());

}

wc.cleanup();

}

} ///:

Представление слов в сортированном виде проще выполнить при хранении данных в TreeMap, который автоматически организует ключи в сортированном порядке (смотрите Главу 9). Когда вы получите набор ключей, используя keySet( ), они также будут отсортированы.

Для открытия файла используется FileReader, а для деления файла на слова, создается StreamTokenizer из FileReader, помещенного в BufferedReader. Для StreamTokenizer, существует стандартный список разделителей, и вы можете добавить еще с помощью нескольких методов. Здесь используется ordinaryChar( ) для того, чтобы сказать: “Этот символ не является тем, чем я интересуюсь”, так что синтаксический анализатор не будет включать его, как часть любого слова, которые он создает. Например, фраза st.ordinaryChar('.') означает, что точка не будет включаться, как часть анализируемого слова. Вы можете найти более подробную информацию в HTML документации по JDK на java.sun.com.

В countWords( ) значащие элементы извлекаются по одному, далее используется информация ttype для определения, что нужно делать с каждым значащим элементом, так как он может быть переводом строки, числом, строкой или единичным символом.

Как только значащий элемент будет найден, опрашивается TreeMap counts на предмет проверки, содержится ли этот элемент как ключевое значение. Если это так, инкрементируется соответствующий объект Counter, указывающий что был найден еще один экземпляр найденного слова. Если нет, создается новый Counter — так как конструктор Counter инициализирует свое значение единицей, то при этом также происходит подсчет слов.

WordCount не является типом TreeMap, так как она не была унаследована. Она выполняет определенный тип функциональности, так что даже хотя методы keys( ) и values( ) должны быть открытыми, это все еще не означает, что должно использоваться наследование, так как некоторые методы TreeMap здесь не подходят. Кроме того, другие методы, такие как getCounter( ), возвращающие Counter для определенной String, и sortedKeys( ), производящие Iterator, завершают изменения в интерфейсе WordCount.

В main( ) вы можете видеть использование WordCount для открытия и подсчета слов в файле — это занимает всего две строчки кода. Затем извлекается итератор сортированного списка ключей (слов), который используется для получения каждого ключа и ассоциированного Count. Вызов cleanup( ) необходим, чтобы быть уверенным в закрытии файла.

StringTokenizer


Хотя он не является частью библиотеки ввода/вывода, StringTokenizer имеет во многом сходную функциональность, что и описанный здесь StreamTokenizer.

StringTokenizer возвращает значащие элементы из строки по одной. Эти значащие элементы являются последовательностью символов, разделенных символами табуляции, пробелами и символами перевода строки. Таким образом, значащими элементами строки “Куда делась моя кошка?” являются “Куда”, “делась”, “моя” и “кошка?”. Как и в случае StreamTokenizer, вы можете настроить StringTokenizer, чтобы он разбивал ввод любым способом, который вам нужен, но с помощью StringTokenizer вы можете сделать это, передав второй аргумент в конструктор, который имеет тип String и является разделителем, который вы хотите использовать. В общем, если вам нужна большая изощренность, используйте StreamTokenizer.

Вы запрашиваете у объекта StringTokenizer следующий значащий элемент строки, используя метод nextToken( ), который возвращает либо следующий значащий элемент, либо пустую строку, которая указывает, что более элементов не осталось.

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

//: c11:AnalyzeSentence.java

// Поиск определенных последовательностей в предложении.

import java.util.*;
public class AnalyzeSentence {

public static void main(String[] args) {

analyze("I am happy about this");

analyze("I am not happy about this");

analyze("I am not! I am happy");

analyze("I am sad about this");

analyze("I am not sad about this");

analyze("I am not! I am sad");

analyze("Are you happy about this?");

analyze("Are you sad about this?");

analyze("It's you! I am happy");

analyze("It's you! I am sad");

}

static StringTokenizer st;

static void analyze(String s) {

prt("\nnew sentence >> " + s);

boolean sad = false;

st = new StringTokenizer(s);

while (st.hasMoreTokens()) {

String token = next();

// Поиск идет до тех пор, пока вы

// не найдете одну из двух начальных элементов:

if(!token.equals("I") &&

!token.equals("Are"))

continue; // В начала цикла while

if(token.equals("I")) {

String tk2 = next();

if(!tk2.equals("am")) // Должно быть после Я

break; // Выход из цикла while

else {

String tk3 = next();

if(tk3.equals("sad")) {

sad = true;

break; // Выход из цикла while

}

if (tk3.equals("not")) {

String tk4 = next();

if(tk4.equals("sad"))

break; // Leave sad false

if(tk4.equals("happy")) {

sad = true;

break;

}

}

}

}

if(token.equals("Are")) {

String tk2 = next();

if(!tk2.equals("you"))

break; // Должно быть после Are

String tk3 = next();

if(tk3.equals("sad"))

sad = true;

break; // Выход из цикла while

}

}

if(sad) prt("Sad detected");

}

static String next() {

if(st.hasMoreTokens()) {

String s = st.nextToken();

prt(s);

return s;

}

else

return "";

}

static void prt(String s) {

System.out.println(s);

}

} ///:

Анализ происходит для каждой строки, происходит вход в цикл while и из строки извлекается значащий элемент. Обратите внимание, что первая инструкция if, которая командует continue (вернуться назад к началу цикла и начать его заново), если значащий элемент не является ни словом "I", ни “Are”. Это означает, что будут извлекаться значащие элементы до тех пор, пока не будет найдено “I” или “Are”. Вы можете решить, что нужно использовать == вместо метода equals( ), но этот оператор не будет работать корректно, так как == сравнивает значения ссылок, а метод equals( ) сравнивает содержимое.

Логика оставшейся части метода analyze( ) заключается в поиске шаблона, с которого начинается фраза “I am sad”, “I am not happy” или “Are you sad?”. Без использования инструкции break этот код был бы еще грязнее, чем он есть. Вы должны знать, что типичный синтаксический анализатор (это примитивный пример одного из них) обычно имеет таблицу таких значащих элементов и часть кода, проходящую по всем состояниям таблицы, после чтения каждого элемента.

Вы должны думать, что StringTokenizer является стенографическим упрощением для определенного вида StreamTokenizer. Однако если вы имеете String, которую вы хотите разбить на элементы, StringTokenizer является слишком ограниченным, все, что вам нужно сделать - это перевести строку в StringBufferInputStream, а затем использовать его для создания более мощного StreamTokenizer.

Проверка стиля капитализации


В этом разделе мы взглянем на более сложный пример использования ввода/вывода в Java, который также использует токенизацию. Этот проект весьма полезен, потому что он выполняет проверку стиля, чтобы убедится, что ваша капитализация соответствует стилю Java, который можно найти на java.sun.com/docs/codeconv/index.html. Он открывает .java файл в текущем директории и извлекает все имена классов и идентификаторов, затем показывает, если какой-то из них не соответствует стилю Java.

Для тех программ, которые откроются корректно, вы сначала должны построить хранилище имен классов для хранения всех имен классов из стандартной библиотеки Java. Вы делаете это путем прохождения по всем поддиректориям с исходным кодом стандартной библиотеки Java и запуском ClassScanner в каждой поддиректории. В качестве аргумента получается файл хранилища (каждый раз используется один и тот же путь и одно и тоже имя), к опция командной строки -a указывает, что имена классов должны добавляться в хранилище.

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

Вы должны знать, что программа не является точной; есть несколько моментов, когда она будет указывать на то, что она считает проблемой, но, взглянув на код, вы увидите, что ничего не нужно менять. Это немного раздражает, но это гораздо легче, чем пытаться найти все эти случаи, пристально вглядываясь в код.

//: c11:ClassScanner.java

// Сканирует все файлы в директории в поисках

// классов и идентификаторов для проверки капитализации.

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

// Не все делает правильно, но достаточно хороший помощник.

import java.io.*;

import java.util.*;
class MultiStringMap extends HashMap {

public void add(String key, String value) {

if(!containsKey(key))

put(key, new ArrayList());

((ArrayList)get(key)).add(value);

}

public ArrayList getArrayList(String key) {

if(!containsKey(key)) {

System.err.println(

"ERROR: can't find key: " + key);

System.exit(1);

}

return (ArrayList)get(key);

}

public void printValues(PrintStream p) {

Iterator k = keySet().iterator();

while(k.hasNext()) {

String oneKey = (String)k.next();

ArrayList val = getArrayList(oneKey);

for(int i = 0; i < val.size(); i++)

p.println((String)val.get(i));

}

}

}
public class ClassScanner {

private File path;

private String[] fileList;

private Properties classes = new Properties();

private MultiStringMap

classMap = new MultiStringMap(),

identMap = new MultiStringMap();

private StreamTokenizer in;

public ClassScanner() throws IOException {

path = new File(".");

fileList = path.list(new JavaFilter());

for(int i = 0; i < fileList.length; i++) {

System.out.println(fileList[i]);

try {

scanListing(fileList[i]);

} catch(FileNotFoundException e) {

System.err.println("Could not open " +

fileList[i]);

}

}

}

void scanListing(String fname)

throws IOException {

in = new StreamTokenizer(

new BufferedReader(

new FileReader(fname)));

// Кажется, не работает:

// in.slashStarComments(true);

// in.slashSlashComments(true);

in.ordinaryChar('/');

in.ordinaryChar('.');

in.wordChars('_', '_');

in.eolIsSignificant(true);

while(in.nextToken() !=

StreamTokenizer.TT_EOF) {

if(in.ttype == '/')

eatComments();

else if(in.ttype ==

StreamTokenizer.TT_WORD) {

if(in.sval.equals("class") ||

in.sval.equals("interface")) {

// Получаем имя класса:

while(in.nextToken() !=

StreamTokenizer.TT_EOF

&& in.ttype !=

StreamTokenizer.TT_WORD)

;

classes.put(in.sval, in.sval);

classMap.add(fname, in.sval);

}

if(in.sval.equals("import") ||

in.sval.equals("package"))

discardLine();

else // Это идентификатор или ключевое слово

identMap.add(fname, in.sval);

}

}

}

void discardLine() throws IOException {

while(in.nextToken() !=

StreamTokenizer.TT_EOF

&& in.ttype !=

StreamTokenizer.TT_EOL)

; // Выбрасываем элемент в конец строки

}

// Кажется, что метод удаления комментариев StreamTokenizer

// сломан. Это извлекает комментарии:

void eatComments() throws IOException {

if(in.nextToken() !=

StreamTokenizer.TT_EOF) {

if(in.ttype == '/')

discardLine();

else if(in.ttype != '*')

in.pushBack();

else

while(true) {

if(in.nextToken() ==

StreamTokenizer.TT_EOF)

break;

if(in.ttype == '*')

if(in.nextToken() !=

StreamTokenizer.TT_EOF

&& in.ttype == '/')

break;

}

}

}

public String[] classNames() {

String[] result = new String[classes.size()];

Iterator e = classes.keySet().iterator();

int i = 0;

while(e.hasNext())

result[i++] = (String)e.next();

return result;

}

public void checkClassNames() {

Iterator files = classMap.keySet().iterator();

while(files.hasNext()) {

String file = (String)files.next();

ArrayList cls = classMap.getArrayList(file);

for(int i = 0; i < cls.size(); i++) {

String className = (String)cls.get(i);

if(Character.isLowerCase(

className.charAt(0)))

System.out.println(

"class capitalization error, file: "

+ file + ", class: "

+ className);

}

}

}

public void checkIdentNames() {

Iterator files = identMap.keySet().iterator();

ArrayList reportSet = new ArrayList();

while(files.hasNext()) {

String file = (String)files.next();

ArrayList ids = identMap.getArrayList(file);

for(int i = 0; i < ids.size(); i++) {

String id = (String)ids.get(i);

if(!classes.contains(id)) {

// Игнорирует идентификаторы длиной 3 или

// более символов, если они все в верхнем регистре

// (эероятно это значения static final):

if(id.length() >= 3 &&

id.equals(

id.toUpperCase()))

continue;

// Проверяется, записан ли первый символ в верхнем регистре:

if(Character.isUpperCase(id.charAt(0))){

if(reportSet.indexOf(file + id)

== -1){ // Еще не включено в отчет

reportSet.add(file + id);

System.out.println(

"Ident capitalization error in:"

+ file + ", ident: " + id);

}

}

}

}

}

}

static final String usage =

"Usage: \n" +

"ClassScanner classnames -a\n" +

"\tAdds all the class names in this \n" +

"\tdirectory to the repository file \n" +

"\tcalled 'classnames'\n" +

"ClassScanner classnames\n" +

"\tChecks all the java files in this \n" +

"\tdirectory for capitalization errors, \n" +

"\tusing the repository file 'classnames'";

private static void usage() {

System.err.println(usage);

System.exit(1);

}

public static void main(String[] args)

throws IOException {

if(args.length < 1 || args.length > 2)

usage();

ClassScanner c = new ClassScanner();

File old = new File(args[0]);

if(old.exists()) {

try {

// Пробуем открыть существующий

// файл свойств:

InputStream oldlist =

new BufferedInputStream(

new FileInputStream(old));

c.classes.load(oldlist);

oldlist.close();

} catch(IOException e) {

System.err.println("Could not open "

+ old + " for reading");

System.exit(1);

}

}

if(args.length == 1) {

c.checkClassNames();

c.checkIdentNames();

}

// Записываем имя класса в хранилище:

if(args.length == 2) {

if(!args[1].equals("-a"))

usage();

try {

BufferedOutputStream out =

new BufferedOutputStream(

new FileOutputStream(args[0]));

c.classes.store(out,

"Classes found by ClassScanner.java");

out.close();

} catch(IOException e) {

System.err.println(

"Could not write " + args[0]);

System.exit(1);

}

}

}

}
class JavaFilter implements FilenameFilter {

public boolean accept(File dir, String name) {

// Strip path information:

String f = new File(name).getName();

return f.trim().endsWith(".java");

}

} ///:

Класс MultiStringMap является инструментом, позволяющим вам ставить в соответствие группу строк и каждое ключевое включение. Он использует HashMap (в этот раз через наследование). В качестве ключевых значений используются единичные строки, которые ставятся в соответствие значению ArrayList. Метод add( ) просто проверяет, есть ли уже такое ключевое значение в HashMap, а если его нет, помещает его туда. Метод getArrayList( ) производит ArrayList определенных ключей, а printValues( ), который особенно полезен для отладки, печатает все значения ArrayList, получая ArrayList.

Для облегчения жизни все имена классов стандартной библиотеки Java помещаются в объект Properties (из стандартной библиотеки Java). Помните, что объект Properties является типом HashMap, который хранит только объекты String и для ключевого значения, и для хранимого элемента. Однако он может быть сохранен на диске и восстановлен с диска в одном вызове метода, так что он идеален в качестве хранилища имен. На самом деле нам нужен только список имен, но HashMap не может принимать null ни для ключевых значений, ни для хранящихся значений. Так что один и тот же объект будет использоваться и для ключа, и для значения.

Для классов и идентификаторов, которые будут обнаружены в определенном директории, используются две MultiStringMapclassMap и identMap. Также, когда запускается программа, она загружает хранилище стандартных имен классов в объект Properties, называемый classes, а когда обнаруживается новое имя класса в локальном директории, то оно добавляется и в classes, и в classMap. Таким образом, classMap может использоваться для обхода всех классов в локальном директории, а classes может использоваться для проверки, является ли текущий значащий элемент именем класса (что указывается определением объекта или началом метода, так как захватывается следующий значащий элемент — до точки с запятой — и помещается в identMap).

Конструктор по умолчанию для ClassScanner создает список имен, используя JavaFilter, показанный в конце файла, который реализует интерфейс FilenameFilter. Затем вызывается scanListing( ) для каждого имени файла.

Внутри scanListing( ) открывается файл исходного кода и передается в StreamTokenizer. В документации есть функции slashStarComments( ) и slashSlashComments( ), предназначенные для отсеивания коментариев, которым передается true, но это выглядит некорректно, так как это плохо работает. Поэтому эти строки закомментированы, а комментарии извлекаются другим методом. Чтобы извлечь комментарий, “/” должен трактоваться как обычный символ, и нужно не позволять StreamTokenizer собирать его как часть комментария, поэтому метод ordinaryChar( ) говорит StreamTokenizer, чтобы он не делал это. Это также верно в отношении точки (“.”), так как мы хотим иметь метод, который бы извлекал индивидуальные идентификаторы. Однако символ подчеркивания, который трактуется StreamTokenizer как индивидуальный символ, должен оставляться как часть идентификатора, так как он появляется в таких значениях типа static final, как TT_EOF, и т. д., очень популярных в этой программе. Метод wordChars( ) принимает диапазон символов, которые вы хотите добавить к остающимся внутри значащего элемента, анализирующегося одним словом. Наконец, когда анализируете однострочный комментарий или обнаруживаете строку, для которой необходимо определить конец строки, то при вызове eolIsSignificant(true) конец строки будет обнаружен раньше, чем он будет получен StreamTokenizer.

Оставшаяся часть scanListing( ) читает и реагирует на значащие элементы, пока не встретится конец файла, которых будет обнаружен, когда nextToken( ) вернет значение final static StreamTokenizer.TT_EOF.

Если значащим элементом является “/”, он потенциально может быть комментарием, так что вызывается eatComments( ), чтобы разобраться с этим. Но нас будут интересовать другие ситуации, когда мы имеем дело со словом, для которого есть несколько специальных случаев.

Если это слово class или interface, то следующий значащий элемент представляет имя класса или интерфейса, и оно помещается в classes и classMap. Если это слово import или package, то нам не нужна оставшаяся часть строки. Все остальное должно быть идентификатором (которые нас интересуют) или ключевым словом (которые нас не интересуют, но все они написаны в нижнем регистре, так что они не портят рассматриваемые нами вещи). Они добавляются в identMap.

Метод discardLine( ) является простым инструментом, ищущим конец строки. Обратите внимание, что при каждом получении значащего элемента вы должны проверять конец строки.

Метод eatComments( ) вызывается всякий раз, когда обнаружен слеш в главном цикле анализа. Однако это не обязательно означает, что обнаружен комментарий, так что должен быть извлечен следующий значащий элемент, чтобы проверить, не является ли он слешем (в этом случае строка пропускается) или звездочкой. Но если это ни то, ни другое, это означает, что тот значащий элемент, который вы только что извлекли, необходимо вернуть в главный цикл анализа! К счастью, метод pushBack( ) позволяет вам “втолкнуть назад” текущий элемент во входной поток, поэтому, когда главный цикл анализа вызовет nextToken( ), то он получит то, что вы только что втолкнули обратно.

По соглашению, метод classNames( ) производит массив из всех имен, содержащихся в classes. Этот метод не используется в программе, но он очень полезен для отладки.

Следующие два метода относятся к тем, в которых действительно идет проверка. В checkClassNames( ), имя класса извлекается из classMap (который, запомните, содержит только имена их этой директории, организованные по именам файлов, так что имя файла может быть напечатано наряду с беспорядочными именами классов). Это выполняется путем получения каждого ассоциированного ArrayList, и прохода по нему в поисках элементов с меленькой первой буквой. Если такой элемент найден, то печатается соответствующее сообщение об ошибке.

В checkIdentNames( ), используется аналогичный подход; каждое имя идентификатора извлекается из identMap. Если имени нет в списке classes, оно трактуется как идентификатор или ключевое слово. Проверяется особый случай: если длина имени идентификатора больше или равна трем, и все символы являются символами верхнего регистра, этот идентификатор игнорируется, потому что, вероятно, это значение static final, такое как TT_EOF. Конечно, это не идеальный алгоритм, но он означает, что вы будете предупреждены обо всех идентификаторах, записанных в верхнем регистре, и находящихся не на месте.

Вместо сообщения о каждом идентификаторе, который начинается с большой буквы, этот метод хранит историю всего, о чем уже сообщил в ArrayList вызов reportSet( ). Это трактует ArrayList , как “набор”, который говорит вам, встречались ли эти экземпляры в наборе. Экземпляры производятся соединением имени файла и идентификатора. Если элемента нет в наборе, он добавляется, после чего делается сообщение.

Оставшаяся часть текста программы занимается методом main( ), занимается обработкой аргументов командной строки и определяет, хотите ли вы создать хранилище имен из стандартной библиотеки Java, или хотите проверить написанный вами код. В обоих случаях он создает объект ClassScanner.

Независимо от того, строите ли вы хранилище, или используете его, вы должны попробовать открыть существующее хранилище. При создании объекта File и проверки существования, вы можете решить, стоит ли открывать файл и загружать (load( )) в Properties список классов classes внутри ClassScanner. (Классы из хранилища добавляются, а не переписываются, к классам, найденным конструктором ClassScanner.) Если вы передадите один аргумент командной строки, это будет означать, что вы хотите выполнить проверку имен классов и имен идентификаторов, но если вы передадите два аргумента (второй начинается с “-a”), тем самым вы построите хранилище имен классов. В этом случае открывается файл вывода и используется метод Properties.save( ) для записи списка в файл, наряду со строками, которые обеспечивают заголовочную информацию файла.

Резюме


Библиотека потоков ввода/вывода java удовлетворяет основным требованиям: вы можете выполнить чтение и запись с консолью, файлом, блоком памяти или даже через Internet (как вы увидите в Главе 15). С помощью интерфейсов вы можете создать новые типы объектов ввода и вывода. Вы также можете использовать простую расширяемость объектов потоков, имея в виду, что метод toString( ) вызывается автоматически, когда вы передаете объект в метод, который ожидает String (ограничение Java на “автоматическое преобразование типов”).

Есть несколько вопросов, оставшихся без ответа в документации и дизайне библиотеке потоков ввода/вывода. Например, было бы неплохо, если бы вы могли сказать, что хотите появление исключения при попытке перезаписи существующего файла, когда вы открываете его для вывода — некоторые системы программирования позволяют вам открыть файл только для вывода, только если он еще не существует. В Java это означает, что вы способны использовать объект File для определения существования файла, потому что, если вы откроете его, как FileOutputStream или FileWriter, он всегда будет перезаписан.

Библиотека потоков ввода/вывода вызывает смешанные чувства; она делает много работы и она компактна. Но если вы не готовы понимать шаблон декоратора, то дизайн становится интуитивно не понятен, поэтому есть простор для дополнительных исследований и обучения. Это то же не все: нет поддержки определенного рода форматированного вывода, который поддерживают практически все пакеты ввода/вывода других языков.

Однако, как только вы поймете шаблоны декорации и начнете использование библиотеки в тех решениях, которые требуют гибкости, вы сможете использовать выгоды дизайна, с точки зрения которого дополнительные строки кода не будут вас беспокоить столь сильно.
1   ...   31   32   33   34   35   36   37   38   39


написать администратору сайта