Как не хардкодить путь к файлу java

Обновлено: 06.07.2024

Если вы хотите написать программу, которая делает что-то с файлами на диске, это у вас очень легко получится. В Java очень много классов, которые помогают вам работать как с самими файлами, так и с их содержимым.

В ранних версиях Java для работы с файлами использовались классы типа File и FileInputStream . Но теперь класс File считается устаревшим, и использовать его не рекомендуется. Вы, конечно, можете еще встретить его в коде, параметрах методов или конструкторах классов.

Мы будем начинать изучение работы с файлами сразу с класса Path . Path — это класс, который пришел на смену File . Работа с ним безопаснее и эффективнее.

Класс Path

Технически Path — это не класс, а интерфейс. Так сделано для того, чтобы можно было под каждую операционную (и файловую) систему писать свой класс-наследник Path .

У Windows свои стандарты написания пути файлов, у Linux — свои. А ведь в мире еще много операционных систем, и у каждой — свои стандарты.

Поэтому везде в методах для работы с файлами указан интерфейс Path , а реально работа идет с его классами-наследниками: WindowsPath , UnixPath , .

Создание объекта Path

Чтобы создать объект Path (на самом деле это будет объект класса-наследника — WindowsPath ), нужно воспользоваться командой вида:

Где имя — это имя переменной типа Path . путь — это путь к файлу (или директории) вместе с именем файла (или директории). А of — статический метод класса Path .

Метод of() используется для того, чтобы создать объекты типа WindowsPath если программа запускается под Windows, а если программа запускается под Linux — объекты UnixPath . Вы не можете создать объект типа Path с помощью кода вида new Path() .

Код Примечание
Путь к файлу
Путь к директории

Файл (или директория) не обязаны существовать, чтобы мог существовать валидный объект типа Path . Может вы только хотите создать файл. Объект типа Path — это как продвинутая версия типа String — он не привязан к конкретному файлу на диске: он просто хранит некий путь на диске и все.

2. Методы типа Path

У интерфейса Path есть довольно много интересных методов. Самые интересные представлены в таблице ниже.

Метод Описание
Возвращает родительскую директорию
Возвращает имя файла без директории
Возвращает корневую директорию из пути
Проверяет, что текущий путь — абсолютный
Преобразует путь в абсолютный
Убирает шаблоны в имени директории.
Строит новый абсолютный путь из абсолютного и относительного.
Получает относительный путь из двух абсолютных путей.
Проверяет, что текущий путь начинается с пути
Проверяет, что текущий путь заканчивается на путь
Дробит путь на части с помощью разделителя / .
Возвращает количество частей.
Дробит путь на части с помощью разделителя / .
Возвращает часть по ее номеру.
Дробит путь на части с помощью разделителя / .
Возвращает часть пути, заданную интервалом.
Преобразует объект Path в устаревший объект File
Преобразует объект Path в объект типа URI

Ниже идет краткое описание существующих методов.

3. Разделение пути на части

Метод getParent() возвращает путь, который указывает на родительскую директорию для текущего пути. Независимо от того, был этот путь директорией или файлом:

Метод getFileName() возвращает одно имя файла (или директории) — то, что идет после последнего разделителя:

Это страшное слово - хардкод. Все боятся это сделать, но иногда каждый из нас это делает.

Но я утверждаю, что хардкод в привычном нам понимании вовсе не так уж страшен, и на самом деле гораздо страшнее, когда в коде прописывают кое-что иное.

Так что же на самом деле нельзя хардкодить?

Классический хардкод

Все обычно считают, что

  • Нельзя хардкодить числа в коде! Надо вынести в константу.
  • Нельзя хардкодить настройки в коде! Надо вынести в файл с настройками (а у некоторых и в базу данных).

То есть если джуниор девелопер напишет в коде if (age >= 23) , ему за это надо дать по рукам. Так обычно считается. Чтобы сберечь руки, он должен срочно вынести “23” в константу типа MINIMUM_LOAN_AGE .

Давайте разбираться в причинах

А почему плохо прописать в коде “23”?

Обычно называют две причины. Их втирают нам в сознание ещё с университетской скамьи.

Когда нужно будет поменять “23” на “24”, её придётся поменять во многих файлах - трудоёмко.

Само по себе “23” плохо читается. Что означает “23” - возраст, длину волос, объём бензобака? Почему именно 23, а не 22 или 24?

Почему эти причины не катят?

Эти причины настолько нам привычны, что мы даже не задумываемся, насколько они актуальны в наше время. Вы удивитесь, но не очень-то актуальны. Прямо скажем, они устарели. Смотрите сами.

Во всех современных IDE очень легко поменять “23” на “24”. Одной кнопкой. Ctrl+R -> Enter. Всё. Хоть у тебя в проекте три файла, хоть три миллиона.

Да, “23” плохо читается. Но часто при вынесении в константу оно не становится более читаемым. Да, название константы MINIMUM_LOAN_AGE говорит о том, что это минимальный возраст, с которого можно брать кредит. Но и выражение if (age >= 23) в методе canRequestLoan() говорит ровно о том же ничуть не хуже.

А почему именно 23, почему не 22 или 24 - это всё равно непонятно. Чтобы это узнать, в наше время легче заглянуть в историю изменений (git -> annotate) или в тесты (Ctrl+Shift+T) - с нашими IDE это легко.

Ладно, ладно

Я знаю, вас переполняют эмоции. Вы хотели бы вбить мне в грудь осиновый кол за такую ересь. Но потерпите, сейчас мы дойдём до главного.

Конечно, всё-таки выносить такие штуки в константы иногда полезно.

Я хотел сказать, что самый страшный хардкод - это вовсе не константы.

А что же - самый страшный хардкод?

Hardcode

Вглядитесь внимательно в это выражение. Все обычно думают, что самый страшный хардкод - это вот:

Hardcode

Но вглядитесь, неужели это действительно самое страшное место? Оглядитесь вокруг, не притаилось ли рядом что-то более опасное? На самом деле самая страшная часть - это вот:

Потому, что вот её-то поменять во всём коде на порядок сложнее. Когда однажды выяснится, что для получения кредита нужно стать старше 23 лет, да ещё и найти работу, нам придётся найти в коде все места, где прописано if (age >= 23) и поменять их на if (age > 23 && employed) . Но как найти все знаки все знаки >= ? Их же тысячи! Вот это ручная работа на столетия!

Но самое страшное, что в коде могут быть и выражения вида

и даже такие места, которые совсем нереально обнаружить:

Что же делать?

Вот почему важно выносить не константы, а логику. Важно следить, чтобы любое знание в коде было прописано ровно в одном месте. В данном случае - знание о том, в каких случаях клиент может взять кредит (то самое >= 23 ) должно быть вынесено в отдельный метод.

И все остальные места должны использовать этот метод. Кажется тривиальным? О нет. Если это знание действительно в одном месте, зачем вы так рьяно хотите вынести “23” в константу?

Упростим

Всё ещё кажется тривиальным? Ок, давайте упростим пример. Забудьте 23. Пусть будет 0.

Я уверен, в вашем коде миллион таких мест:

И я таких видел миллион. if balance > 0 прописан и на странице платежей, и на странице кредитов, и депозитов, и т.д.

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

Но тут… опачки. Оказывается, что в десяти местах прописано if (balance > 0) , в ещё двадцати - if (balance <= 0) , а в грёбаном яваскрипте и вовсе if (account.balance) .

И вот тут-то начинаются проблемы. Все эти места нужно анализировать отдельно. В некоторые из них нужно добавить && !arrested , а в некоторые не нужно - ведь там речь идёт не о платежах.

Я не придумываю, это абсолютно реальный пример.

Юнит-тесты

Очевидный плюс вынесения логики в методы - её легко тестировать.

Поначалу этот тест кажется даже избыточным и даже бесполезным:

Но всё меняется, как только добавляются ньвые требования:

Пэдж обжекты

Всё ещё кажется, что для разумных людей это очевидные вещи?

Тогда посмотрите на пэдж обжекты - воплощение константного антипаттерна во всей красе! Миллионы людей выносят локаторы в константы и даже не задумываются, что что-то здесь не так…

В Java, как мне получить абсолютный путь к файлу, пожалуйста?

Вы хотите написать плагин для Maven? Или вы хотите получить доступ к файлу во время выполнения вашей программы?

Вы можете использовать ClassLoader.getResource метод, чтобы получить правильный ресурс.

Хотя это может работать не все время, более простое решение -

Вы можете создать File объект и использовать getAbsolutePath метод:

Совет ограниченного использования, так как он полагается на рабочий каталог как на корень maven. И даже в этом случае вам лучше использовать target/classes/abc.txt ссылку на файл, поскольку это каноническое место, куда Maven помещает файлы ресурсов после обработки (например, плагин maven-resources мог выполнить подстановку свойств в abc.txt). Намного лучше использовать abc.txt через getResource () из пути к классам. Что делать, если конечный пользователь запускает ваше приложение как исполняемый файл JAR? Тогда физического Файла вообще не будет. Это еще одна причина, по которой вы должны использовать getResource () и, например, открывать для него входной поток в зависимости от того, что вы хотите с ним делать. Можно ли это убрать как правильный ответ? @Karol S ответил ниже - это должен быть правильный ответ (отсюда расхождение в голосовании) Неверный ответ. Пожалуйста, обратитесь к ответу @Karol ниже

Правильный способ, который действительно работает:

Теперь не имеет значения, где физически находится файл в пути к классам, он будет найден, если ресурс на самом деле является файлом, а не записью JAR.

(Кажущееся очевидным new File(resource.getPath()) не работает для всех путей! Путь все еще закодирован в URL!)

Или, предположительно, вы могли бы просто сделать: new File(resource.toURI()).getAbsolutePath(); (т.е. я не думаю, что вам нужен объект Path?) Хороший совет о toURI (), это позволяет избежать пробелов на вашем пути, выходящих как% 20! Благодарность! У меня это почти сработало. Но мне пришлось внести одно изменение: YourClass.class.getClassLoader (). GetResource ("abc"); Вы могли бы поступить иначе, new File(YourClass.class.getResource("abc").toURI().getPath()) если бы захотели. Я не понимаю, как это сработало для стольких людей без начального слэша: .getResource("/abc")

Вам нужно указать путь, начинающийся с /

Создайте экземпляр classLoader нужного вам класса, тогда вы сможете легко получить доступ к файлам или ресурсам. теперь вы получаете доступ к пути, используя getPath() метод этого класса.

Чтобы вернуть файл или путь к файлу

На нашем пути к абсолютному пути есть две проблемы:

  1. Найденное место размещения будет не там, где лежат исходные файлы, а там, где сохранен класс. И папка ресурсов почти наверняка будет где-то в исходной папке проекта.
  2. Те же функции для получения ресурса работают по-разному, если класс выполняется в подключаемом модуле или в пакете непосредственно в рабочей области.

Следующий код даст нам все полезные пути:

Здесь исследуются обе функции, которые можно использовать для локализации папки ресурсов. Что касается class , его можно получить как статически, так и динамически.

Если проекта нет в подключаемом модуле, код при запуске в JUnit напечатает:

Итак, чтобы добраться до src / rest / resources, мы должны перемещаться вверх и вниз по дереву файлов. Можно использовать оба метода. Обратите внимание, мы не можем использовать getResource(resourceFolderName) , потому что эта папка не находится в целевой папке. Надеюсь, никто не кладет ресурсы в созданные папки.

Если класс находится в пакете, который находится в плагине, результат того же теста будет:

Итак, мы снова должны идти вверх и вниз по дереву папок.

Наиболее интересен случай, когда пакет запускается в плагине. В качестве теста плагина JUnit для нашего примера. Результат:

Здесь мы можем получить абсолютный путь, только объединив результаты обеих функций. И этого мало. Между ними мы должны поместить локальный путь к месту, где находятся пакеты классов, относительно папки плагина. Возможно, вам придется что-то вставить как src или src/test/resource сюда.

Просто положите сам ChromeDriver.exe в папку, которая указана в переменной PATH.

Тогда путь к нему вообще нигде в коде указывать не надо будет, ни через System.setProperty, ни через дополнительные библиотеки и т.п..

Просто положите сам ChromeDriver.exe в папку, которая указана в переменной PATH.

Тогда путь к нему вообще нигде в коде указывать не надо будет, ни через System.setProperty, ни через дополнительные библиотеки и т.п..

интересно тогда, и в гит его коммитить тогда надо будет, и на ЦИ сервере будет всегда крутиться только одна старая версия, да и с переменной PATH на ЦИ сервере морочиться

не говоря о том, что не будет работать у коллег которые клонируют себе этот проект

Просто положите сам ChromeDriver.exe в папку, которая указана в переменной PATH.

Тогда путь к нему вообще нигде в коде указывать не надо будет, ни через System.setProperty, ни через дополнительные библиотеки и т.п..

интересно тогда, и в гит его коммитить тогда надо будет, и на ЦИ сервере будет всегда крутиться только одна старая версия, да и с переменной PATH на ЦИ сервере морочиться

не говоря о том, что не будет работать у коллег которые клонируют себе этот проект

Я считаю, что обновление версии браузера и драйвера - в любом случае должны быть осознанными и целенаправленными изменениями.

И да, сам драйвер у меня так же вместе с проектом в репозиторий. Но: а) репозиторий локальный (в нашей внутренней сети), во "внешний" интернет ничего не лезет в поисках новых версий, б) я могу гарантировать, что все будет работать стабильно, т.к. драйвер не поменяется внезапно из-за того, что появилась более свежая версия.

У коллег, использующих мой проект - проблем не возникает, т.к. об этом принципе использования драйвера они и так знают.

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

Я никого не заставляю делать также.

Я считаю, что обновление версии браузера и драйвера - в любом случае должны быть осознанными и целенаправленными изменениями.

версия выставляется в конфиге webdrivermanager, вот её потом и надо "осознанно менять"

можно советовать другим стаканом носить воду из колодца, и аргументировать тем что "я никого не заставляю делать так же" и "это ещё один вариант переноса воды из колодца"

получается осознанное усложнение проекта, теперь вместо простого клонирования и запуска - ещё надо и сконфигурировать операционную систему

Я считаю, что обновление версии браузера и драйвера - в любом случае должны быть осознанными и целенаправленными изменениями.

версия выставляется в конфиге webdrivermanager, вот её потом и надо "осознанно менять"

можно советовать другим стаканом носить воду из колодца, и аргументировать тем что "я никого не заставляю делать так же" и "это ещё один вариант переноса воды из колодца"

получается осознанное усложнение проекта, теперь вместо простого клонирования и запуска - ещё надо и сконфигурировать операционную систему

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

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

Вы даже не поинтересовались у автора вопроса - использует ли он Maven, есть ли у него доступ к интернету там, где запуск производится (некоторые запускают на отдельных серверах, у некоторых по соображениям безопасности - куча ограничений по связи с внешним миром, ситуации могут быть самые разные).

Не нравится Вам моё решение - Ваше право. Озвучены варианты, автор сможет придти и выбрать тот, который считает для себя оптимальным.

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

тут для проекта требуется ещё и настройка ОС, так что это по-любому усложнение, а не упрощение. Да ещё видимо проект привязан к какой-то одной ОС, причём какой-то одной разрядности (32 или 64) и не будет работать на других без "допиливания"

простой проект - это когда клонировал и запустил, причём проект запустится одинаково без проблем и на 32х и на 64х битных системах, и под виндой и под макосью и под убунтой

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

не завод, а "колодец должен подгрузить ведро, которое совместимо с водоносом". а если привязать какое-то одно ведро, то только у "совместимых водоносов" будет работать, да ещё и с требованием чтобы водонос "сам настроился под именно это ведро"

Читайте также: