Уменьшите объем памяти, заменив анонимный класс на singleton. Но нужно больше реорганизовать дизайн

#java #refactoring #singleton

#java #рефакторинг #singleton

Вопрос:

Итак, у меня есть этот метод, который выполняется неоднократно

 public static boolean isReady(String dirPath, int numPdfInPrintJob){
    File dir = new File(dirPath);
    String[] fileList = dir.list(new FilenameFilter(){
        public boolean accept(File file, String filename) {
            return (filename.toLowerCase().endsWith(".pdf"));
        }
    });
    if(fileList.length >= numPdfInPrintJob) return true;
    else return false;  
}
  

Этот метод использует anonymous class , который будет создавать новый экземпляр FilenameFilter при каждом вызове, и я часто использую этот метод. Итак, я хочу превратить это anonymous class в singleton . Итак, моя первоначальная мысль — создать новый singleton класс, который будет выглядеть следующим образом

 public class PdfFileNameFilter implements FilenameFilter{
    private PdfFileNameFilter(){} //non-instantible

    //guarantee to only have one instance at all time
    public static final PdfFileNameFilter INSTANCE = new PdfFileNameFilter();

    public boolean accept(File dir, String name) {
        return (name.toLowerCase().endsWith(".pdf"));
    }
}
  

Могу ли я еще немного реорганизовать это. Мне также нужно сделать ZipFileNameFilter , и, возможно, много разных фильтров расширений файлов. Не хочу создавать класс для каждого фильтра. Мне нужно еще немного реорганизовать этот дизайн. Может быть, interface где-то здесь это встанет на место.

Комментарии:

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

2. Вы уверены, что вам нужно это делать? Создание новых объектов на Java обходится очень дешево, поэтому я бы не пытался оптимизировать там.

3. @Jeff: На самом деле я этого не profile делал. Это просто мысль. Поскольку я знаю, что этот метод часто используется. Так что, возможно, было бы неплохо уменьшить объем памяти, занимаемый этим. Возможно, это не изменит ситуацию, но я не вижу, что она не может ухудшиться.

4. @tangens: Я переосмыслю это. Но если я просто создам статический метод, как Peter предлагается ниже, это определенно немного оптимизировало бы, правильно. Я не понимаю, как это может ухудшиться, правильно?

5. Объем памяти, вероятно, должен вас беспокоить, поскольку GC удалит все ненужные экземпляры для вас. Вам следует беспокоиться о том, сколько операций выполняет метод, если он вызывается много раз, и любой из ответов, использующих частный статический экземпляр, поможет вам уменьшить количество действий, которые должен выполнять ваш метод. Просто имейте в виду, что это не должно считаться медленным, вам нужно профилировать это, чтобы знать наверняка.

Ответ №1:

Если все, что вы хотели сделать, это уменьшить использование памяти, вы могли бы сделать

 private static final FilenameFilter PDF_FILES = new FilenameFilter(){
    public boolean accept(File file, String filename) {
        return (filename.toLowerCase().endsWith(".pdf"));
    }
}
  

Если вы хотите создать singleton, самый простой способ

 public enum PdfFileNameFilter implements FilenameFilter {
    INSTANCE;

    public boolean accept(File dir, String name) {
        return (name.toLowerCase().endsWith(".pdf"));
    }
}
  

Комментарии:

1. 1 Первый вариант кажется мне самым простым выбором. Зачем создавать singleton, если он все равно используется только в 1 классе.

2. @Robin, я согласен. В моей IDE вы можете взять исходный код и нажать <ctrl> <alt> C для «Ввести константу», и она предложит имя для константы (на основе типа и имени параметра) и найти любые другие места в коде, которые также можно заменить.

Ответ №2:

Мне кажется проще просто использовать ваш существующий анонимный класс и создать один экземпляр, который используют все ваши вызовы методов.

 private static final FilenameFilter PDF_FILTER = new FilenameFilter() {
    public boolean accept(File file, String filename) {
        return (filename.toLowerCase().endsWith(".pdf"));
    }
}

public static boolean isReady(String dirPath, int numPdfInPrintJob){
    File dir = new File(dirPath);
    String[] fileList = dir.list(pdfFilter);
    if(fileList.length >= numPdfInPrintJob) return true;
    else return false;  
}
  

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

Комментарии:

1. Я полностью согласен с этим — зачем тратить время на внедрение свойства singleton в сам класс, когда вы можете просто создать только один экземпляр

Ответ №3:

Для этого вы могли бы использовать enum :

 public enum ExtensionFilter implements FilenameFilter {
    PDF(".pdf"),
    ZIP(".zip");

    private final String extension;

    private ExtensionFilter(String extension) {
        this.extension = extension;
    }

    @Override
    public boolean accept(File dir, String name) {
        return (name.toLowerCase().endsWith(extension));
    }
}
  

Теперь вы сможете использовать его как:

 dir.list(ExtensionFilter.PDF)
  

Вы также можете выполнить итерацию по ним, если вам нужно:

 for ( FilenameFilter fileNameFilter : ExtensionFilter.values() ) {
    ....
}
  

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

 public enum ExtensionFilter implements FilenameFilter {
    PDF,
    ZIP(".zip", ".jar", ".war", ".ear");

    private final String[] extensions;

    private ExtensionFilter(String... extensions) {
        if (extensions.length == 0) {
            extensions = new String[] {"."   name().toLowerCase()};
        }
        this.extensions = extensions;
    }

    @Override
    public boolean accept(File dir, String name) {
        for (String extension : extensions) {
            if (name.toLowerCase().endsWith(extension)) {
                return true;
            }
        }
        return false;
    }
}
  

Комментарии:

1. Если singleton является излишеством (а я бы сказал, что это так), то перечисление (особенно когда он не указал необходимость фильтрации zip) будет казаться излишним.

2. @CajunLuke: Он действительно указал на необходимость фильтрации zip: «Мне также нужно использовать ZipFileNameFilter и, возможно, множество других фильтров расширений файлов». И его утверждение заключается в том, что создание нового экземпляра каждый раз было излишним, а singleton был решением .

3. Спасибо. Мне нравится ваш ответ, и rachet очень. Спасибо. Итак, я предполагаю, что в коде я бы просто сделал это dir.list(DefaultFileNameFilter.PDF); для фильтрации pdf , правильно?

4. 1 для простого и эффективного использования перечислений. Здесь вообще нет излишеств, и решение элегантное, эффективное и простое для понимания

Ответ №4:

вы можете отказаться от полного singleton и использовать личное поле для настройки расширения

 public class ExtensionFileNameFilter implements FilenameFilter{
    private String extension;
    private ExtensionFileNameFilter (String extension){this.extension=extension;} 

    public static final ExtensionFileNameFilter PDFINSTANCE = new ExtensionFileNameFilter (".pdf");
    public static final ExtensionFileNameFilter ZIPINSTANCE = new ExtensionFileNameFilter (".zip");
    //add instances as you need

    public boolean accept(File dir, String name) {
        return (name.toLowerCase().endsWith(extension));
    }
}
  

Комментарии:

1. Большое вам спасибо. Мне это очень нравится. Спасибо.

2. Разве это не просто самоделка enum ? Отсутствует возможность использовать имя константы в виде строки, повторять константы, читать ordinal() , сериализовать и другие…

3. @costi это так, но есть возможность создать фабричный метод с использованием (слабой ссылки) hashmap для повторного использования экземпляров и создания новых

4. Это можно сделать точно так же в enum … за исключением создания новых экземпляров типа enum , но вы можете создавать новые FilenameFilter экземпляры.

Ответ №5:

Могу ли я еще немного реорганизовать это.

Да, да, вы можете.

Предполагая, что это не тот ответ, который вы искали (вам следует обновить yoru question, чтобы задать более конкретный вопрос), я бы не стал его реорганизовывать, пока он вам не понадобится; YAGNI.

Как только у вас будет больше кода, например, больше фильтров имен файлов, вы увидите возможные рефакторинги. Вы увидите общий код, возможно, интерфейс, что-то в этом роде. Не пытайтесь преждевременно перенастроить.

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

Ответ №6:

Для интереса, эта альтернативная accept реализация будет выполняться намного быстрее в тестовом тестировании. Он не создает новые объекты с сохранением состояния и не несет других накладных расходов String.toLowerCase , которые не требуются в вашем случае.

   public boolean accept(File file, String filename) {
    int offset = s.length() - 4;
    if (offset >= 0) {
      if (s.charAt(offset) == '.') {
        offset  = 1;
        if (s.regionMatches(offset, "pdf", 0, 3)) {
          return true;
        } else if (s.regionMatches(offset, "PDF", 0, 3)) { 
          return true;
        }
      }
    }
    return false;
  }
  

Если бы это была точка доступа к выполнению, и вы искали оптимизацию, что-то подобное помогло бы.

Комментарии:

1. хммм. Интересно. Строка неизменяема, поэтому toLowerCase создаст другой объект. Я думаю, что в случае загрузки большого количества файлов это определенно приведет к уменьшению объема памяти. Но вы сказали, что это также будет работать быстрее? Даже если я просто загружу 1 файл, это будет работать быстрее?

2. Он будет работать быстрее, но если у вас не будет большого количества вызовов, вы этого не заметите. Для меня исходные регистры выполняли миллисекунды где-то около 5000 вызовов; оптимизированная версия не регистрируется примерно до 200000. Это может не иметь отношения к вашему приложению — вам скажет только профилирование. Но в горячих точках выполнения создание объектов, таких как Strings, и вызов таких методов, как toLowerCase (дорогостоящих, даже если без new String них), будут более значимыми, чем создание объектов без состояния с помощью анонимных внутренних классов, которые, я думаю, будут исчезающе незначительными.

3. Большое вам спасибо. Это здорово знать. Какой инструмент профилирования вы используете?

4. В прошлом я использовал JProfiler. Для приведенных выше показателей я просто использую простые циклы в основном методе и записываю время до и после использования System.currentTimeMillis. int invocations = 0; long start = System.currentTimeMillis(); boolean b = false; for (int i = 0; i < 1000000; i ) { for (int j = 0; j < names.length; j ) { b = matches(names[j]); invocations ; } } long duration = System.currentTimeMillis() - start; System.err.println(b); System.err.println(duration " (" invocations " invocations)");