Я использовал некоторые проприетарные фреймворки и библиотеки, которые используют объекты File и только объекты File, повсюду в интерфейсах.
Нет ничего плохого в том, чтобы иметь методы, принимающие объекты File, но не стоит принимать только это. Например, анализатор XML DOM / SAX в JDK принимает объекты File, но включает и другие источники, такие как InputStream , URL и т. Д. Класс Properties даже не принимает объекты File. На самом деле, класс File не очень популярен в интерфейсах хороших библиотек Java, включая JRE.
Причина проста: InputStream гораздо более абстрактный, чем File.
Когда вы указываете, например, [1] , такой интерфейс:
public void sendMail(String email , String message, File attachment);
Вы приковали пользователя цепью, чтобы указать путь к существующему файлу на локальном диске.
Теперь представьте, что вашему приложению необходимо отправить электронное письмо о критической ошибке, происходящей на производстве, чтобы уведомить системных администраторов, и вы хотите включить трассировку стека Исключения, чтобы помочь отследить проблему. Единственный доступный вариант — распечатать трассировку стека во временный файл, вызвать метод, а затем удалить файл.
Это потому, что вложение — это «контент», а не файл. Также конфигурация приложения — это «контент», а не файл. Файл XML и значки ваших приложений — это не просто файлы, это данные и ресурсы. И данные, ресурсы и содержимое, они могут поступать не только из Файлов, но и из удаленных Систем (URL), из баз данных, из JAR-файла в classpath или из других систем, которые вы можете даже не представить.
В приведенном выше примере интерфейс будет намного лучше, если он определен так:
public void sendMail(String email, String message, InputStream attachment);
Теперь … если вы хотите прикрепить трассировку стека, вы можете сделать что-то вроде:
try { ... } catch (Exception ex) { sendMail( "[email protected]", "something bad happened!", new ThrowableInputStream(ex)); }
Насколько сложно преобразовать исключение в InputStream? Это оно:
public class ThrowableInputStream extends InputStream { private byte[] bytes; private int i = 0; public ThrowableInputStream(Throwable throwable) { this.bytes = toByteArray(throwable); } private byte[] toByteArray(Throwable throwable) { ByteArrayOutputStream output = new ByteArrayOutputStream(); PrintStream printer = new PrintStream(output); try { throwable.printStackTrace(printer); printer.flush(); return output.toByteArray(); } finally { printer.close(); } } @Override public int read() throws IOException { if (i < bytes.length) return bytes[i++]; else return -1; } }
Выше или проще распечатать исключение во временный файл? И вышеупомянутое менее или более эффективно? Если вы предпочитаете временный файл … по крайней мере, не забудьте удалить файл. И не забудьте проверить, доступна ли файловая система для записи … и убедиться, что на диске осталось достаточно места … и т. Д. И т. Д. …
Вы хотите прикрепить некоторый XML-контент, который вы получили в формате String? Опять же, вместо печати String во временный файл и т. Д. И т. Д. … вы можете написать класс, который преобразует Strings в InputStream:
public class StringInputStream extends InputStream { private String string; private int i; public StringInputStream(String string) { this.string = string; } @Override public int read() throws IOException { if (i < string.length()) return string.charAt(i++); else return -1; } }
Всего 16 строк кода.
Невероятная сила URL
Также конфигурация вашего приложения является частью интерфейса с пользователями … и здесь файлы имеют свои недостатки.
Но давайте пойдем дальше со сложным вариантом использования: вам нужно проанализировать некоторые XML-данные, развернутые внутри zip-файла на удаленном веб-сервере.
У вас есть два варианта:
- Вы можете записать URL файла на веб-сервере в вашу конфигурацию. Ваша программа получит URL из конфигурации, получит доступ к веб-серверу с помощью http-клиента, прочитает заархивированный файл, сохранит его где-нибудь, распакует его, затем проанализирует xml и будет управлять данными. И, наконец, навести порядок.
- Или вы можете указать в своей конфигурации URL-адрес, например «jar: http: //www.partnerwebsite.com/zippedfile.zip! Path / to / data.xml», и передать этот URL-код в анализатор XML. Готово.
Теперь … если вы выбрали второй вариант и требования вашего приложения изменились, и вам нужен доступ к одному и тому же XML-файлу, в сжатом виде или нет, с веб-сервера, с сервера ftp, из локальной файловой системы и т. Д., Вам просто нужно изменить URL в конфигурации. И, если вам нужно получить доступ к ресурсу в протоколе, который изначально не поддерживается URL, вы можете реализовать URLStreamHandler .
Да, URL очень мощный и гибкий в Java. И они возвращают вам InputStream . Не файл! Это не жертва.
Как «починить» библиотеку с такими недостатками?
Ладно … ты несчастный парень, как и я, который должен использовать плохие библиотеки более или менее часто. Как вы исправляете интерфейс как первый метод sendMail? (тот, что с файлом)
Нередко такие плохо спроектированные библиотеки ДЕЙСТВИТЕЛЬНО плохие. Поэтому вы можете написать собственный (объектно-ориентированный) интерфейс поверх него: оболочку или что-то в этом роде. Ваш интерфейс будет использовать InputStream, конечно. Поэтому вам нужно что-то, что преобразует ваш InputStream в файл, который может быть переварен плохой библиотекой.
Вот пример:
public class FileDump { private static final Random random = new Random(); public static File dump(InputStream input, String fileName) { File path = createTempDir(); return dump(input, path, fileName); } private static File createTempDir() { String tmpDirName = System.getProperty("java.io.tmpdir"); File tmpDir = new File(tmpDirName); File newTempDir = new File(tmpDir, String.valueOf(random.nextInt())); newTempDir.mkdirs(); return newTempDir; } private static File dump(InputStream input, File path, String fileName) { File file = new File(path, fileName); try { OutputStream output = new FileOutputStream(file); try { int data; while ((data = input.read()) != -1) output.write(data); } finally { output.close(); } return file; } catch (IOException ex) { throw new RuntimeException(ex); } } }
Приведенный выше класс является примером. Вы можете улучшить его, проверяя, есть ли место на диске, что целевой каталог доступен для записи и т. Д. Затем не забудьте удалить файл после того, как плохая библиотека использовала содержимое файла.
Выводы
- В следующий раз, когда вы определяете интерфейс библиотеки и, по любой причине, вы импортируете java.io.File … подумайте об использовании (также) InputStream [2], пожалуйста.
- Если вы склонны написать в своей конфигурации «путь к файлу» ресурса, который вам нужно прочитать, подумайте о возможности использовать URL-адрес с протоколом file: /path/to/file.ext. Кстати, имейте в виду, что, к сожалению, вы не можете писать в URL-адрес Java независимо от протокола, но вы все равно можете получить путь к файлу из URL-адреса, используя схему URI «file» .
Заметки
[1] Приведенный выше пример не является хорошим интерфейсом объекта почтовой программы. Это просто для того, чтобы объяснить, почему указывать файл как вложение не очень хорошая идея.
[2] Приведенное выше обсуждение действительно не только для InputStream и OutputStream, но также для Reader и Writer. Вы должны использовать Reader / Writer для работы с текстовыми данными, а InputStream / OutputStream для работы с двоичными данными.
От http://en.newinstance.it/2010/09/10/using-files-in-your-interfaces-is-not-a-good-idea