2015-10-26 4 views
1

Например, некоторый метод имеет следующую реализацию:Как реализовать реализацию, если входной параметр изменен?

void setExcludedCategories(List<Long> excludedCategories) { 
    if (excludedCategories.contains(1L)) { 
     excludedCategories.remove(1L); 
    } 
} 

И это называется следующим способом:

setExcludedCategories(Array.asList(1L, 2L, 3L)); 

Конечно, это приведет к ВЗУ java.lang.UnsupportedOperationException исключения, когда попытается удалить элемент.

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

UPD: Спасибо за ответы. Подведем итоги:

  1. Всегда создавайте новый ArrayList из списка входных данных, чтобы убедиться, что он изменен - ​​будет использоваться много бесполезной памяти -> НЕТ.
  2. Поймать неподдерживаемое исключение.
  3. Укажите в JavaDoc, что вызывающий объект не должен передавать неизменяемый список - кто-нибудь читает JavaDoc? Когда что-то не работает только :)
  4. Не используйте Arrays.asList() в коде вызывающего абонента - это вариант, если вы являетесь владельцем этого кода, но в любом случае вы должны знать, разрешает ли этот конкретный метод неизменность или нет (см. 3).

Кажется, что второй вариант - единственный способ решить эту проблему.

+0

Очевидно, я не знаю, что делает ваш код, помимо кода примера, который вы опубликовали, но было бы возможно скопировать данные из параметра и поместить его в измененный список? –

+0

Вам не нужен этот оператор 'if'. Совершенно безопасно и легально удалить что-то из коллекции, даже если ее нет в коллекции. – VGR

+0

Да, это очевидный способ исправить это, но это приведет к дополнительному использованию памяти. Лучшее исправление не позволит вообще передавать неизменяемый список. Поэтому я прошу о возможных способах, как это сделать. –

ответ

2

Как я могу изменить этот код, чтобы убедиться, что входной параметр excludedCategories поддерживает удаление?

В общем случае вы не можете. Учитывая произвольный класс, который реализует API List, вы не можете указать (статически или динамически), если поддерживаются дополнительные методы.

Вы можете использовать тесты instanceof, чтобы проверить, соответствует ли класс списка , чтобы реализовать этот метод или не выполнять его. Например, ArrayList и LinkedList do, но Collections.UnmodifiableList нет. Проблема в том, что ваш код может столкнуться с классами классов, которые ваши тесты не покрывают. (Особенно, если это библиотека, которая предназначена для повторного использования в приложениях других людей.)

Вы также можете попробовать проверить поведение ранее неизвестных классов; например создайте тестовый экземпляр, попробуйте remove, чтобы узнать, что произойдет, и запишите поведение в Map<Class, Boolean>. Есть две проблемы:

  • Возможно, вы не сможете (правильно) создать экземпляр класса списка для его проверки.

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

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

2

Одним словом, вы не можете знать. Если объект реализует интерфейс (например, List), вы не можете знать, действительно ли он будет делать то, что ожидается для всех методов. Например, Collections.unmodifiableList() возвращает List, который выбрасывает UnsupportedOperationException. Он не может быть отфильтрован через подпись метода, если вы хотите получить другие версии List.

Лучшее, что вы можете сделать, это бросить IllegalArgumentException для известных подтипов, которые не поддерживают то, что вы хотите. И поймать UnsupportedOperationException для других типов корпусов. Но на самом деле вы должны javadoc ваш метод с тем, что требуется, и что он бросает IllegalArgumentException в других случаях.

+0

Вопрос здесь не в обработке исключений. Я думаю, что это API, который создает неизменный список и, следовательно, exceptoin при удалении, который является java.lang.UnsupportedOperationException –

+0

Да, дело не в обработке исключений. Вот почему мой ответ сказал, что вы не можете знать. Я обновлю ответ. –

0

Arrays.asList(T... a) возвращает List<java.util.Arrays.ArrayList<E>>, что является неизменным списком. Для того, чтобы получить код работает только обернуть результат с java.util.ArrayList<T>, как показано ниже

setExcludedCategories(new ArrayList<Long>(Arrays.asList(1L, 2L, 3L))); 
+0

Да, я подумал об этом, но потом мне нужно обернуть списки входных данных в ** везде **, потому что я не знаю, будет ли кто-то передавать неизменный список здесь. И в то же время это приведет к накладным расходам памяти - всегда создавайте список массивов, даже если этот список изменен. –

+0

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

+0

Извините, что написано слишком поздно. Меня поразило, что ваш контракт всегда может обрабатывать исключение, поэтому, если передается неизменяемая коллекция, просто перехватывайте исключение времени выполнения и обрабатывайте его соответствующим образом. Он также не нарушает принципы кодирования, поскольку непреложный список является исключительным случаем, и они не являются частью контракта. –

1

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

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

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

1

Вы можете поймать исключение в классе утилиты, как в приведенном ниже примере (как упоминалось выше). Плохо, что вам нужно делать вставку/удаление, чтобы проверить, будет ли исключение. Вы не можете использовать instanceof, поскольку все классы Collections.Unmodifiablexxx имеют доступ по умолчанию.

CollectionUtils:

import java.util.List; 

public class CollectionUtils { 

    public <T> boolean isUnmodifiableList(List<T> listToCheck) { 

     T object = listToCheck.get(0); 

     try { 
      listToCheck.remove(object); 
     } catch (UnsupportedOperationException unsupportedOperationException) { 
      return true; 
     } 
     listToCheck.add(0, object); 
     return false; 
    } 
} 

Главная:

import java.util.Arrays; 
import java.util.List; 

public class Main { 

    private static final CollectionUtils COLLECTION_UTILS = new CollectionUtils(); 

    public static void main(String[] args) { 
     setExcludedCategories(Arrays.asList(1L, 2L, 3L)); 


    } 

    private static void setExcludedCategories(List<Long> excludedCategories) { 
     if (excludedCategories.contains(1L)) { 
      if(!COLLECTION_UTILS.<Long>isUnmodifiableList(excludedCategories)){ 
       excludedCategories.remove(1L); 
      } 

     } 
    } 


} 
+0

Хорошая идея! Но я беспокоюсь о производительности этого решения: удалите, а затем добавьте элемент обратно - он должен что-то стоить. –

+0

@RomanProshin да, мы не знаем, будет ли проверен связанный список или арраист, поэтому в любом случае производительность влияет. Я также упомянул в ответ. Hashcode и equals override - это еще одна история, что все элементы могут быть удалены, но я думаю, что это не по теме :) – HRgiger

0
  1. Всегда создавать новый ArrayList из списка ввода, чтобы убедиться, что это изменчивое - много бесполезных памяти будет использоваться -> NO.

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

И не обращая внимания, что его единственное прочный и inutitively понимать идиомы.

Единственной приемлемой альтернативой могло бы быть явное изменение имени вашего метода (таким образом, общение его поведения лучше), в примере, который вы показываете, назовите его «removeExcludedCategories», если оно предназначено для изменения списка аргументов (но не объектов государство).

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

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

Смежные вопросы