JavaRush /Java блог /Архив info.javarush /Константы и интернационализация
Joysi
41 уровень

Константы и интернационализация

Статья из группы Архив info.javarush
При работе программа опирается в вычислениях или вводе-выводе используется не только переменные, но и явно прописанные константы. Допустим Вы обрабатываете финансовые данные и по ходу кода несколько раз выводите результаты (пусть в консоль) примерно таким образом: Константы и интернационализация - 1

System.out.println("Итоговое количество =" + countLoans);
...
String strCount = "Итоговое количество :" + loans.calc(); 
...
System.out.println(strCount + strSumDesc);
И это весьма плохо по нескольким причинам.
  1. Нарушение принципа DRY (Don’t repeat yourself — не повторяй).
    Дублирование текста и, как следствие, увеличение размеров скомпилированного кода. Также при этом возможны описки.

  2. Нарушение принципа KISS (keep it simple stupid — делайте вещи проще).
    В случае необходимости изменения такого рода констант придется прошерстить весь код.

  3. Каждое изменение константы потребует перекомпиляции кода.

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

Первые две причины устранит ввод констант с модификаторами static final и их инициализацией + применение их по ходу кода. Т.е. создаем:

public class Constants {
    public static final int COUNT_DEPARTMENTS = 20;
    public static final String MSG_TOTAL_AMOUNT = "Итоговое количество";
    ...
}
и применяем примерно как System.out.println(Constants.MSG_TOTAL_AMOUNTCOUNT_DEPARTMENT + "= " + countLoans); Если нам необходимо поменять фразу, мы делаем только в одном месте. Чтобы устранить третью причину можно воспользоваться классом Properties, более подробно есть на соответствующих лекциях JavaRush и некоторых big-задачах второй половины 20-х уровней. Наиболее часто употребляемые переменные можно вынести как отдельные public static final, реже используемые. Получать через вызов метода PROPS.getProperty().

public class Constants {
    public static final int COUNT_DEPARTMENTS;
    public static final String MSG_TOTAL_AMOUNT;
    public static final Properties PROPS;

    static {
        Logger log = LogManager.getLogger(Constants.class);
        PROPS = new Properties();
        try{
            fis = new FileInputStream("confs/config.properties");
            PROPS.load(fis);
            fis.close();
        } catch ( IOException e) {
            log.error(e.getMessage());
        }
        MSG_TOTAL_AMOUNT = PROPS.getProperty("msg.total.amount");
        COUNT_DEPARTMENTS = Integer.parseInt(PROPS.getProperty("count.departments"));
        ...
    }
}
Осталось разобраться с интернационализацией. Сделаем так, чтобы в static блоке происходила загрузка нужно файла properties. Для этого будем работать с ResourceBundle. Отметим, что из ResourceBundle стандартными методами можно считывать значения ключей, но не устанавливать их. Поэтому по прежнему храним в отдельном Properties (config.properites) файле параметры, не имеющие отношение к интернационализации (к тому же некоторые из низ возможно менять по ходу действия программы). Например, язык интерфейса. Удобный интерфейс IDEA нам поможет. Создаем его: Константы и интернационализация - 2Добавляем нужные языки: Константы и интернационализация - 3В структуре проекта для IDEA отображается как: Константы и интернационализация - 3Наполняем содержимым: Константы и интернационализация - 5

public class Constants {
    public static final int COUNT_DEPARTMENTS;
    public static final String MSG_TOTAL_AMOUNT;
    public static final Properties PROPS;
    public static final ResourceBundle UI_LANGUAGE;

    static {
        Logger log = LogManager.getLogger(Constants.class);
        PROPS = new Properties();
        try{
            fis = new FileInputStream("confs/config.properties");
            PROPS.load(fis);
            fis.close();
        } catch ( IOException e) {
            log.error(e.getMessage());
        }
        if (PROPS.getProperty("ui.language").equalsIgnoreCase("RUS"))
            UI_LANGUAGE = ResourceBundle.getBundle("messages", CharsetControl.RUS);
        else
            UI_LANGUAGE = ResourceBundle.getBundle("messages", CharsetControl.ENG);
        
        MSG_TOTAL_AMOUNT = UI_LANGUAGE.getString("msg.total.amount");
        COUNT_DEPARTMENTS = Integer.parseInt(PROPS.getProperty("count.departments")); 
        ...
    }
}
Мы добились, что язык интерфейса меняется без вмешательства в код. К тому же мы получили четкое разделение труда: можем отдать русскийproperties-файл переводчику-гуманитарию, который в удобном для себя текстовом редакторе его переведет. Нам останется только переименовать его в нужное имя файла и разместить обратно в ResourceBundle. Небольшое дополнение (использование CharsetControl): Properties файлы не чувствительны к кодировке (обрабатывают содержимое файла исключительно по одному байту, что равносильно использованию ISO8859-1) и кириллица в Properties-файла будет отображена некорректно. Чтобы избежать этого, можно:
  • Перегонять кириллические символы в ESCAPE-последовательности \uxxxx через native2ascii

  • Помочь Properties передав ему необходимый ResourceBundle.Control, в котором нам необходимо перегрузить метод (код нашел на просторах интернета + немного модифицировал).

Возможно кому то поможет:

import java.io.*;
import java.net.URL;
import java.net.URLConnection;
import java.nio.charset.Charset;
import java.security.*;
import java.util.*;

public class CharsetControl extends ResourceBundle.Control {

    public static final CharsetControl UTF_8 = new CharsetControl("utf8");
    public static final CharsetControl RUS = new CharsetControl("cp1251");
    public static final Locale    ENG = new Locale("en","GB");

    private Charset charset;

    public CharsetControl(String charset) {
        this(Charset.forName(charset));
    }

    public CharsetControl(Charset charset) {
        this.charset = charset;
    }

    public Charset getCharset() {
        return charset;
    }

    @Override
    public ResourceBundle newBundle(String baseName, Locale locale, String format, ClassLoader loader, boolean reload)
            throws IllegalAccessException, InstantiationException, IOException {

        String bundleName = toBundleName(baseName, locale);
        ResourceBundle bundle = null;
        if (format.equals("java.class")) {
            try {
                Class<? extends ResourceBundle> bundleClass  =
                        (Class<? extends ResourceBundle>)loader.loadClass(bundleName);

                if (ResourceBundle.class.isAssignableFrom(bundleClass)) {
                    bundle = bundleClass.newInstance();
                } else {
                    throw new ClassCastException(bundleClass.getName()
                            + " cannot be cast to ResourceBundle");
                }
            } catch (ClassNotFoundException e) {
            }
        } else if (format.equals("java.properties")) {
            final String resourceName = toResourceName(bundleName, "properties");
            final ClassLoader classLoader = loader;
            final boolean reloadFlag = reload;
            InputStream stream = null;
            try {
                stream = AccessController.doPrivileged(
                        new PrivilegedExceptionAction<InputStream>() {
                            public InputStream run() throws IOException {
                                InputStream is = null;
                                if (reloadFlag) {
                                    URL url = classLoader.getResource(resourceName);
                                    if (url != null) {
                                        URLConnection connection = url.openConnection();
                                        if (connection != null) {
                                            connection.setUseCaches(false);
                                            is = connection.getInputStream();
                                        }
                                    }
                                } else {
                                    is = classLoader.getResourceAsStream(resourceName);
                                }
                                return is;
                            }
                        });
            } catch (PrivilegedActionException e) {
                throw (IOException) e.getException();
            }
            if (stream != null) {
                try {
                    bundle = new PropertyResourceBundle(new InputStreamReader(stream, getCharset()));
                } finally {
                    stream.close();
                }
            }
        } else {
            throw new IllegalArgumentException("unknown format: " + format);
        }
        return bundle;
    }
}
Комментарии (1)
ЧТОБЫ ПОСМОТРЕТЬ ВСЕ КОММЕНТАРИИ ИЛИ ОСТАВИТЬ КОММЕНТАРИЙ,
ПЕРЕЙДИТЕ В ПОЛНУЮ ВЕРСИЮ
Li Уровень 20
29 августа 2023
а что должно быть config.properties? или это промежуточный properties...