Класс ResourceBundle
Запросили английские строки: ResourceBundle.getBundle("text", Locale.US). Файла text_en.properties нет, зато есть text.properties и text_ru.properties — и на компьютере с русской локалью Java вернёт русский текст, а не базовый файл. Почему так происходит и как это отключить, разберём в разделе о порядке поиска.
Класс ResourceBundle (java.util.ResourceBundle) — это набор локализованных ресурсов (строк и объектов), которые Java загружает для нужной локали по базовому имени. Вместе с классом Locale он лежит в основе интернационализации Java-приложений: тексты интерфейса хранятся отдельно от кода, и новый язык добавляется новым файлом без перекомпиляции.
Формат properties-файла
Properties-файл (расширение .properties) — это текстовый файл, где каждая строка задаёт пару «ключ = значение», а строки, начинающиеся с # или !, считаются комментариями. Каждый файл набора содержит одни и те же ключи, переведённые для своей локали.
Ключи принято группировать через точку:
# Комментарий
group1.key1 = value1
group1.key2 = value2
group2.key1 = value3 Например, английский вариант:
label.button = submit
label.field = login
message.welcome = Welcome! и русский вариант с теми же ключами:
label.button = принять
label.field = логин
message.welcome = Добро пожаловать! Пробелы вокруг = игнорируются, вместо = можно использовать :. Длинное значение переносится на следующую строку символом \ в конце строки.
Имена файлов и метод getBundle()
Все файлы одного набора ресурсов имеют общее базовое имя, к которому через символ подчёркивания добавляются код языка и код страны: text_uk_UA.properties. Загружает набор статический метод ResourceBundle.getBundle(baseName, locale), где baseName — базовое имя без суффиксов и без расширения.
Пусть базовое имя — text. Тогда набор ресурсов может выглядеть так:
text.properties — базовый файл (значения по умолчанию)
text_ru_RU.properties — русский язык, Россия
text_uk_UA.properties — украинский язык, Украина
text_fr_CA.properties — французский язык, Канада Следующий фрагмент загружает набор ресурсов с базовым именем text для французского языка и страны Канада:
Locale locale = Locale.of("fr", "CA");
ResourceBundle rb = ResourceBundle.getBundle("text", locale); Метод Locale.of() появился в Java 19, а конструктор new Locale("fr", "CA") с этой версии помечен как устаревший. В Java 8–18 используйте new Locale("fr", "CA") или Locale.forLanguageTag("fr-CA"). Подробнее о создании локалей — в уроке Использование класса Locale.
Файлы ищутся в classpath, а не в рабочей папке: в Maven/Gradle-проекте их кладут в src/main/resources. Если файлы лежат в пакете, базовое имя указывается полностью через точки: файл com/example/i18n/text_fr.properties загружается вызовом getBundle("com.example.i18n.text", locale). Вызов getBundle("text") без локали использует Locale.getDefault().
Порядок поиска набора ресурсов
Метод getBundle() ищет файлы от самого точного имени к общему: сначала язык и страна запрошенной локали, затем только язык, затем то же для локали по умолчанию и лишь в конце базовый файл без суффиксов. Используется первый найденный файл, а более общие становятся его «родителями» для недостающих ключей.
Пусть базовое имя — text, запрошена локаль fr_CA, а локаль по умолчанию JVM — uk_UA. Вызов ResourceBundle.getBundle("text", Locale.of("fr", "CA")) перебирает кандидатов в таком порядке:
text_fr_CAtext_frtext_uk_UA— локаль по умолчаниюtext_uktext— базовый файл
Именно поэтому в примере из начала урока для Locale.US вернулся text_ru.properties: файлов text_en_US и text_en нет, и до базового файла Java проверила локаль по умолчанию ru_RU. Исключение — запрос Locale.ROOT: для него сразу берётся базовый файл.
Locale.setDefault(Locale.of("ru", "RU"));
// Файлы: text.properties, text_ru.properties
ResourceBundle rb = ResourceBundle.getBundle("text", Locale.US);
System.out.println("[" + rb.getLocale() + "]");
// Вывод: [ru]
ResourceBundle rb2 = ResourceBundle.getBundle("text", Locale.US,
ResourceBundle.Control.getNoFallbackControl(ResourceBundle.Control.FORMAT_DEFAULT));
System.out.println("[" + rb2.getLocale() + "]");
// Вывод: [] — загружен базовый text.properties (Locale.ROOT) Control.getNoFallbackControl() отключает шаг с локалью по умолчанию, и после text_en поиск сразу переходит к базовому файлу. Это полезно на сервере, где локаль JVM не связана с языком пользователя.
Неочевидный момент
Методы getBundle() с параметром ResourceBundle.Control не поддерживаются в именованных модулях (Java 9+, есть module-info.java) и бросают UnsupportedOperationException. В модульном приложении вместо Control используют интерфейс java.util.spi.ResourceBundleProvider.
Кириллица в properties: Java 8 и Java 9+
С Java 9 класс PropertyResourceBundle читает properties-файлы в кодировке UTF-8, поэтому кириллицу можно писать в файле как есть. В Java 8 и раньше файлы читались в ISO-8859-1, и русские или украинские буквы нужно было записывать escape-последовательностями \uXXXX, иначе вместо текста выводились «кракозябры».
| Версия Java | Кодировка по умолчанию | Как записать «Привет» | Нюанс |
|---|---|---|---|
| Java 8 и ниже | ISO-8859-1 | \u041f\u0440\u0438\u0432\u0435\u0442 | Конвертация утилитой native2ascii или настройкой IDE (в IntelliJ IDEA — Transparent native-to-ascii conversion) |
| Java 9+ | UTF-8 (JEP 226) | Привет | Если файл не является корректным UTF-8, он перечитывается как ISO-8859-1; утилита native2ascii из JDK 9 удалена |
Escape-последовательности \uXXXX работают во всех версиях, поэтому старые файлы после перехода на Java 9+ переделывать не нужно. Вернуть прежнее поведение можно системным свойством -Djava.util.PropertyResourceBundle.encoding=ISO-8859-1.
Важно
Переход на UTF-8 касается только ResourceBundle. Метод Properties.load(InputStream) и в Java 21 читает ISO-8859-1, поэтому тот же файл с кириллицей через Properties прочитается с искажениями. Для UTF-8 передавайте Reader: props.load(new InputStreamReader(in, StandardCharsets.UTF_8)).
Пример: строки для разных локалей
Программа ниже загружает один и тот же набор ресурсов text для трёх локалей и выводит значения ключей str1 и str2. Ключи в коде одинаковые, меняется только локаль, а значит и файл, из которого берётся текст.
Файлы в src/main/resources:
# text.properties
app.name = ExamClouds
str1 = Hello
str2 = Default bundle
# text_en_US.properties
str1 = Hello
str2 = USA
# text_uk_UA.properties
str1 = Привіт
str2 = Україна import java.util.Locale;
import java.util.ResourceBundle;
public class ResourceBundleExample1 {
public static void main(String[] args) {
printInfo(Locale.ROOT);
printInfo(Locale.of("en", "US"));
printInfo(Locale.of("uk", "UA"));
}
private static void printInfo(Locale locale) {
ResourceBundle rb = ResourceBundle.getBundle("text", locale);
System.out.println(rb.getString("str1"));
System.out.println(rb.getString("str2"));
System.out.println();
}
} Вывод:
Hello
Default bundle
Hello
USA
Привіт
Україна Чтобы обойти все ключи набора, используется метод keySet(). Он возвращает ключи не только найденного файла, но и его родителей, поэтому для uk_UA в списке есть и app.name из базового text.properties:
import java.util.Locale;
import java.util.ResourceBundle;
import java.util.TreeSet;
public class ResourceBundleExample2 {
public static void main(String[] args) {
printInfo(Locale.ROOT);
printInfo(Locale.of("uk", "UA"));
}
private static void printInfo(Locale locale) {
ResourceBundle rb = ResourceBundle.getBundle("text", locale);
for (String key : new TreeSet<>(rb.keySet())) {
System.out.println(key + " = " + rb.getString(key));
}
System.out.println();
}
} Вывод:
app.name = ExamClouds
str1 = Hello
str2 = Default bundle
app.name = ExamClouds
str1 = Привіт
str2 = Україна Совет
Порядок ключей в keySet() не определён и не совпадает с порядком строк в файле. Если порядок важен (вывод меню, отчёт, тест), сортируйте ключи, например через new TreeSet<>(rb.keySet()), как в примере выше.
Методы класса ResourceBundle
Основные методы ResourceBundle делятся на три группы: загрузка набора (getBundle()), чтение значений по ключу (getString(), getObject(), getStringArray()) и обход ключей (keySet(), getKeys(), containsKey()). Отсутствующий ключ приводит к исключению, а не к null.
| Метод | Что делает | Граничный случай |
|---|---|---|
static getBundle(String baseName, Locale locale) | Загружает набор ресурсов для локали (есть перегрузки без локали, с ClassLoader и Control) | Не найден ни один файл — MissingResourceException |
getString(String key) | Возвращает значение по заданному ключу, при необходимости из родительского набора | Нет ключа — MissingResourceException; значение не строка — ClassCastException |
getObject(String key) | Возвращает значение любого типа (актуально для ListResourceBundle) | Нет ключа — MissingResourceException |
getStringArray(String key) | Возвращает массив строк | Для properties-файлов не подходит: значение там всегда String, будет ClassCastException |
keySet() | Возвращает Set всех ключей набора и его родителей | Порядок ключей не определён |
getKeys() | Возвращает Enumeration ключей — старый вариант keySet() | Тоже включает ключи родителей |
containsKey(String key) | Проверяет наличие ключа в наборе или его родителях | Удобен перед getString(), чтобы не ловить исключение |
getLocale() | Возвращает локаль реально загруженного файла | Для базового файла — Locale.ROOT (пустая строка) |
static clearCache() | Очищает кэш загруженных наборов | Нужен, если файлы меняются без перезапуска JVM |
MissingResourceException: когда возникает
MissingResourceException — непроверяемое исключение (наследник RuntimeException), которое ResourceBundle бросает в двух случаях: getBundle() не нашёл ни одного файла набора, включая базовый, или getString() не нашёл ключ ни в файле, ни в его родителях.
ResourceBundle rb = ResourceBundle.getBundle("text", Locale.ROOT);
System.out.println(rb.containsKey("str9"));
// Вывод: false
rb.getString("str9");
// java.util.MissingResourceException: Can't find resource for bundle
// java.util.PropertyResourceBundle, key str9
ResourceBundle.getBundle("labels", Locale.of("fr", "CA"));
// java.util.MissingResourceException: Can't find bundle for base name labels, locale fr_CA Чтобы getBundle() не падал на незнакомой локали, всегда держите базовый файл без суффиксов (text.properties) в дополнение к локализованным (text_en_US.properties, text_ru_RU.properties), и пусть в нём будут все ключи. Если исключение возникает, хотя файл существует, чаще всего виновато его расположение: файл не попал в classpath или базовое имя указано без пакета.
ListResourceBundle и PropertyResourceBundle
ResourceBundle — абстрактный класс с двумя стандартными реализациями: PropertyResourceBundle читает строки из properties-файлов, а ListResourceBundle берёт ресурсы из Java-класса и может хранить объекты любого типа. Метод getBundle() для каждого имени-кандидата сначала ищет класс, затем properties-файл.
| Реализация | Источник данных | Типы значений | Когда использовать |
|---|---|---|---|
PropertyResourceBundle | Файл text_fr.properties | Только String | Тексты интерфейса — переводчики правят файлы без программиста |
ListResourceBundle | Класс text_fr extends ListResourceBundle | Любые объекты: числа, массивы, иконки | Ресурсы, которые нельзя выразить строкой, или значения, вычисляемые в коде |
import java.util.ListResourceBundle;
public class Messages_fr extends ListResourceBundle {
@Override
protected Object[][] getContents() {
return new Object[][] {
{"greeting", "Bonjour"},
{"maxUsers", 100}
};
}
} ResourceBundle rb = ResourceBundle.getBundle("Messages", Locale.FRENCH);
System.out.println(rb.getString("greeting"));
int max = (Integer) rb.getObject("maxUsers");
System.out.println(max);
// Вывод:
// Bonjour
// 100 Если рядом с классом Messages_fr лежит и файл Messages_fr.properties, будет загружен класс, а файл проигнорирован. Имя класса должно полностью совпадать с именем кандидата, включая пакет.
Часто задаваемые вопросы
Куда положить properties-файлы, чтобы ResourceBundle.getBundle() их нашёл?
В classpath: в Maven и Gradle — в папку src/main/resources. Базовое имя передаётся без расширения .properties, а если файл лежит в пакете, то с пакетом через точки: getBundle("com.example.i18n.text"). Путь к файлу на диске вида "C:/app/text" не сработает. Подробнее — в разделе Имена файлов и метод getBundle().
Чем ResourceBundle отличается от класса Properties?
Properties — изменяемая таблица из одного файла, который вы открываете и загружаете сами. ResourceBundle — неизменяемый набор файлов для разных локалей: он сам выбирает подходящий файл, берёт недостающие ключи у родителя, кэширует результат и с Java 9 читает UTF-8. Для настроек приложения обычно берут Properties, для переводов интерфейса — ResourceBundle.
Почему изменения в properties-файле не видны без перезапуска программы?
Метод getBundle() кэширует загруженные наборы, и повторный вызов возвращает объект из кэша. Сбросить кэш можно вызовом ResourceBundle.clearCache(), после чего следующий getBundle() перечитает файлы. Тонкую настройку времени жизни кэша даёт метод Control.getTimeToLive() (вне именованных модулей).
Как подставить значения в строку из ResourceBundle?
Запишите в файле шаблон с номерами параметров, например greeting = Привет, {0}! У вас {1} новых сообщений, и отформатируйте его через java.text.MessageFormat: MessageFormat.format(rb.getString("greeting"), "Анна", 3) вернёт «Привет, Анна! У вас 3 новых сообщений». Одинарная кавычка в таком шаблоне служит экранирующим символом, поэтому апостроф пишут двумя кавычками: it''s.
Practice
Reinforce this lesson with hands-on tasks, each with a level and a time estimate.
- Темы Java на русском и английском (ResourceBundle)
- Класс FullReport: отчет с заголовком на языке локали
- Чек с ценами в валюте текущей локали
Video Explanation
Prefer video format? Watch this lesson with examples and explanations.
Comments