Клон () действительно когда-либо используется? Что относительно обороны, копирующей в методах считывания/методах set?

  1. Сначала откройте свое первое решение
  2. перейдите к файлам, а затем к последнему решению, затем найдите свое решение
  3. Ctrl + отметьте решение, которое хотите открыть
8
задан Bakudan 6 November 2012 в 22:11
поделиться

8 ответов

Из книги Джоша Блоха «Эффективная Java»:

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

Правило 24: Делайте защитные копии, когда это необходимо

11
ответ дан 5 December 2019 в 08:00
поделиться

For both of these issues, the point is explicit control of state. It may be that most of the time you can "get away" without thinking about these things. This tends to be less true as your application gets larger and it gets harder to reason about state and how it propagates among objects.

You've already mentioned a major reason why you'd need to have control over this - being able to use the data safely while another thread was accessing it. It's also easy to make mistakes like this:

class A {
   Map myMap;
}


class B {
   Map myMap;
   public B(A a)
   {
        myMap = A.getMap();//returns ref to A's myMap
   }
    public void process (){ // call this and you inadvertently destroy a
           ... do somethign destructive to the b.myMap... 
     }
}

The point is not that you always want to clone, that would be dumb and expensive. The point is not to make blanket statements about when that sort of thing is appropriate.

3
ответ дан 5 December 2019 в 08:00
поделиться

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

Date now = new Date();
someObject.setDate(now);
// another use of "now" that expects its value to not have changed

, то у вас потенциально могут возникнуть две проблемы:

  1. someObject потенциально может изменить значение « now », то есть описанный выше метод, когда он позже использует это переменная может иметь другое значение, чем ожидалось, и
  2. , если после передачи « сейчас » в someObject вы измените ее значение, и если someObject не изменился сделайте защитную копию, затем вы изменили внутреннее состояние someObject .

Вы должны либо защитить себя от обоих случаев, либо или вы должны задокументировать свои ожидания в отношении того, что разрешено или запрещено, в зависимости от того, кто является клиентом кода. Другой случай - когда у класса есть Map , и вы предоставляете геттер для самой Map . Если Карта является частью внутреннего состояния объекта и объект ожидает полного управления содержимым Карты , то вы не должны никогда выпустил карту . Если вы должны предоставить геттер для карты, верните Collections.unmodifiableMap (myMap) вместо myMap . Здесь вы, вероятно, не захотите делать клон или защитную копию из-за потенциальной стоимости. Возвращая вашу карту, упакованную так, чтобы ее нельзя было изменить, вы защищаете свое внутреннее состояние от изменения другим классом.

По многим причинам clone () часто не является правильным решением. Вот несколько лучших решений:

  1. Для геттеров:
    1. Вместо того, чтобы возвращать Map , возвращайте только Iterator s либо в keySet , либо в Map.Entry или в любой другой позволяет клиентскому коду делать то, что ему нужно. Другими словами, верните что-то, что по сути является представлением вашего внутреннего состояния только для чтения, или
    2. Верните свой объект изменяемого состояния, завернутый в неизменяемую оболочку, подобную Collections.unmodifiableMap ()
    3. Вместо того, чтобы возвращать Карта , предоставляет метод get , который принимает ключ и возвращает соответствующее значение из карты. Если все клиенты будут получать значения с помощью карты , то не давайте клиентам саму карту ; вместо этого предоставьте геттер, который обертывает Map метод get () .
      1. Используйте конструкторы копирования в своих конструкторах объектов, чтобы сделать копию всего переданного, что является изменяемым.
      2. Спроектируйте, чтобы принимать неизменяемые величины в качестве аргументов конструктора, когда это возможно, а не изменяемые количества. Иногда имеет смысл взять длинное значение, возвращаемое, например, new Date (). GetTime () , а не объектом Date .
      3. Сделайте так, чтобы ваше состояние final , но помните, что объект final все еще может быть изменен, а массив final все еще может быть изменен.

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

    Вот тривиальный пример плохого кода:

    import java.util.Date;
    
    public class Test {
      public static void main(String[] args) {
        Date now = new Date();
        Thread t1 = new Thread(new MyRunnable(now, 500));
        t1.start();
        try { Thread.sleep(250); } catch (InterruptedException e) { }
        now.setTime(new Date().getTime());   // BAD!  Mutating our Date!
        Thread t2 = new Thread(new MyRunnable(now, 500));
        t2.start();
      }
    
      static public class MyRunnable implements Runnable {
        private final Date date;
        private final int  count;
    
        public MyRunnable(final Date date, final int count) {
          this.date  = date;
          this.count = count;
        }
    
        public void run() {
          try { Thread.sleep(count); } catch (InterruptedException e) { }
          long time = new Date().getTime() - date.getTime();
          System.out.println("Runtime = " + time);
        }
      }
    }
    

    Вы должны увидеть, что каждый запускаемый спит на 500 мс, но вместо этого вы получаете неверную информацию о времени. Если вы измените конструктор, чтобы сделать защитную копию:

        public MyRunnable(final Date date, final int count) {
          this.date  = new Date(date.getTime());
          this.count = count;
        }
    

    , вы получите правильную информацию о времени. Это банальный пример. Вы не хотите отлаживать сложный пример.

    ПРИМЕЧАНИЕ: общий результат сбоя в правильном управлении состоянием - это ConcurrentModificationException при итерации по коллекции.

    ] Следует ли защищать код? Если вы можете гарантировать , что одна и та же небольшая группа опытных программистов всегда будет писать и поддерживать ваш проект, что они будут постоянно работать над ним, чтобы сохранить в памяти детали проекта, что то же самое люди будут работать над этим на протяжении всего срока существования проекта, и что проект никогда не станет «большим», тогда, возможно, вам удастся избежать этого. Но затраты на защитное программирование невелики, за исключением самых редких случаев, а выгода велика. Плюс: защитное кодирование - хорошая привычка. Вы не хотите поощрять развитие дурных привычек передавать изменяемые данные в места, где их не должно быть. Этот когда-нибудь укусит вас. Конечно, все это зависит от требуемого времени безотказной работы вашего проекта.

5
ответ дан 5 December 2019 в 08:00
поделиться

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

1
ответ дан 5 December 2019 в 08:00
поделиться

Я могу вспомнить одну ситуацию, когда клонирование намного предпочтительнее копирующих конструкторов. Если у вас есть функция, которая принимает объект типа X, а затем возвращает его измененную копию, может быть предпочтительнее, чтобы эта копия была клоном, если вы хотите сохранить внутреннюю, не связанную с X информацией. Например, функция, увеличивающая Дата на 5 часов, может быть полезна, даже если ей был передан объект типа SpecialDate . Тем не менее, в большинстве случаев использование композиции вместо наследования позволит полностью избежать подобных проблем.

1
ответ дан 5 December 2019 в 08:00
поделиться

Мне не нравится метод clone (), потому что всегда требуется приведение типа. По этой причине я большую часть времени использую конструктор копирования. В нем более четко указано, что он делает (новый объект), и вы можете полностью контролировать его поведение или глубину копии.

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

0
ответ дан 5 December 2019 в 08:00
поделиться

Одна вещь, которую я всегда упускал из виду при «обсуждении защитного копирования», - это аспект производительности. Это обсуждение, IMHO, является прекрасным примером производительности по сравнению с удобочитаемостью / безопасностью / надежностью.

Защитные копии отлично подходят для оды ropbust. Но если вы используете его в критичной по времени части вашего приложения, это может стать серьезной проблемой для производительности. Недавно у нас было это обсуждение, в котором вектор данных хранит свои данные в виде значений типа double []. getValues ​​() вернул значения. clone (). В нашем алгоритме getValues ​​() вызывается для множества различных объектов. Когда мы задавались вопросом, почему этот простой фрагмент кода выполнялся так долго, мы проверили код - заменили возвращаемые значения .clone () на возвращаемые значения, и внезапно общее время выполнения уменьшилось до менее чем 1/10 от исходного. ценность. Что ж - мне не нужно говорить, что мы решили пропустить защиту.

Примечание: я больше не являюсь защитными копиями в целом. Но используйте свой мозг при clone () ing!

0
ответ дан 5 December 2019 в 08:00
поделиться

Я начал использовать следующую практику:

  1. Создавайте конструкторы копирования в ваших классах, но делайте их защищенными. Причина этого в том, что создание объектов с помощью оператора new может привести к различным проблемам при работе с производными объектами.

  2. Создайте интерфейс Copyable следующим образом:

     public interface Copyable<T> {
            public T copy();
     }

Пусть метод копирования классов, реализующий Copyable, вызывает конструктор защищенного копирования. Затем производные классы могут вызывать super.Xxx (obj_to_copy); для использования конструктора копирования базового класса и добавления дополнительных функций по мере необходимости.

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

0
ответ дан 5 December 2019 в 08:00
поделиться
Другие вопросы по тегам:

Похожие вопросы: