Подтвердить что ты не робот

Переменные Java-экземпляра и локальные переменные

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

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

Спасибо и извините, если я написал в замешательстве.

4b9b3361

Ответ 1

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

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

public class BadIdea {
   public Enum Color { GREEN, RED, BLUE, PURPLE };
   public Color[] map = new Colors[] { 
        Color.GREEN, 
        Color.GREEN, 
        Color.RED, 
        Color.BLUE, 
        Color.PURPLE,
        Color.RED,
        Color.PURPLE };

   List<Integer> indexes = new ArrayList<Integer>();
   public int counter = 0;
   public int index = 0;

   public void findColor( Color value ) {
      indexes.clear();
      for( index = 0; index < map.length; index++ ) {
         if( map[index] == value ) {
            indexes.add( index );
            counter++;
         }
      }
   }

   public void findOppositeColors( Color value ) {
      indexes.clear();
      for( index = 0; i < index < map.length; index++ ) {
         if( map[index] != value ) {
            indexes.add( index );
            counter++;
         }
      }
   }
}

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

BadIdea idea = new BadIdea();
idea.findColor( Color.RED );
idea.findColor( Color.GREEN );  // whoops we just lost the results from finding all Color.RED

Так как findColor использует переменные экземпляра для отслеживания возвращаемых значений, мы можем вернуть только один результат за раз. Попробуйте и сохраните ссылку на эти результаты, прежде чем мы снова назовем это:

BadIdea idea = new BadIdea();
idea.findColor( Color.RED );
List<Integer> redPositions = idea.indexes;
int redCount = idea.counter;
idea.findColor( Color.GREEN );  // this causes red positions to be lost! (i.e. idea.indexes.clear()
List<Integer> greenPositions = idea.indexes;
int greenCount = idea.counter;

В этом втором примере мы сохранили красные позиции на третьей строке, но то же самое произошло! Почему мы их потеряли?! Поскольку idea.indexes был очищен вместо выделенного, то может быть только один ответ, используемый за раз. Вы должны полностью завершить этот результат, прежде чем называть его снова. Когда вы снова вызовете метод, результаты будут очищены, и вы потеряете все. Чтобы исправить это, вам придется выделять новый результат каждый раз, так что красные и зеленые ответы являются отдельными. Поэтому давайте клонировать наши ответы для создания новых копий вещей:

BadIdea idea = new BadIdea();
idea.findColor( Color.RED );
List<Integer> redPositions = idea.indexes.clone();
int redCount = idea.counter;
idea.findColor( Color.GREEN );
List<Integer> greenPositions = idea.indexes.clone();
int greenCount = idea.counter;

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

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

Теперь попробуйте сделать это с помощью двух разных методов. Обратите внимание, как я был "умным", и я повторно использовал те же переменные экземпляра для "сохранения памяти" и сохранил код.; -)

BadIdea idea = new BadIdea();
idea.findColor( Color.RED );
List<Integer> redPositions = idea.indexes;
int redCount = idea.counter;
idea.findOppositeColors( Color.RED );  // this causes red positions to be lost again!!
List<Integer> greenPositions = idea.indexes;
int greenCount = idea.counter;

То же самое произошло! Черт, но я был таким "умным" и экономия памяти, а код использует меньше ресурсов!!! Это реальная опасность использования переменных экземпляра, таких как методы вызова, теперь зависит от порядка. Если я изменю порядок вызова метода, результаты будут разными, даже если я не изменил базовое состояние BadIdea. Я не изменил содержание карты. Почему программа дает разные результаты при вызове методов в другом порядке?

idea.findColor( Color.RED )
idea.findOppositeColors( Color.RED )

Производит другой результат, чем если бы я заменил эти два метода:

idea.findOppositeColors( Color.RED )
idea.findColor( Color.RED )

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

И это касается только однопоточных проблем. Если BadIdea используется в многопоточной ситуации, ошибки могут стать действительно причудливыми. Что произойдет, если findColors() и findOppositeColors() вызывают одновременно? Crash, все ваши волосы выпадают, Смерть, пространство и время сворачиваются в сингулярность, и вселенная поглощается? Вероятно, по крайней мере два из них. Потоки, вероятно, сейчас над вашей головой, но, надеюсь, мы можем оттолкнуть вас от плохого дела, поэтому, когда вы попадаете в темы, эти плохие практики не вызывают у вас настоящей душевной боли.

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

Хотя это может выглядеть как инкапсуляция, это полная противоположность, потому что технические детали того, как вы ее написали, должны быть известны вызывающей стороне. Вызывающий должен написать свой код очень определенным образом, чтобы заставить их работать, и они не могут этого сделать, не зная о технических деталях вашего кода. Это часто называют Leaky Abstraction, потому что класс должен скрывать технические детали за абстракцией/интерфейсом, но технические подробности просачиваются, заставляя вызывающего абонента изменять свое поведение. Каждое решение имеет некоторую степень протечки, но используя любой из вышеперечисленных методов, таких как эти гарантии, независимо от того, какую проблему вы пытаетесь решить, будет ужасно непроходимым, если вы их примените. Поэтому давайте посмотрим на GoodIdea.

Перепишем с помощью локальных переменных:

 public class GoodIdea {
   ...

   public List<Integer> findColor( Color value ) {
      List<Integer> results = new ArrayList<Integer>();
      for( int i = 0; i < map.length; i++ ) {
         if( map[index] == value ) {
            results.add( i );
         }
      }
      return results;
   }

   public List<Integer> findOppositeColors( Color value ) {
      List<Integer> results = new ArrayList<Integer>();
      for( int i = 0; i < map.length; i++ ) {
         if( map[index] != value ) {
            results.add( i );
         }
      }
      return results;
   }
 }

Это исправляет каждую проблему, о которой мы говорили выше. Я знаю, что я не отслеживаю счетчик или возвращаю его, но если бы я сделал, я мог бы создать новый класс и вернуть его вместо List. Иногда я могу использовать следующий объект для быстрого получения нескольких результатов:

public class Pair<K,T> {
    public K first;
    public T second;

    public Pair( K first, T second ) {
       this.first = first;
       this.second = second;
    }
}

Длинный ответ, но очень важная тема.

Ответ 2

Используйте переменные экземпляра, когда это основная концепция вашего класса. Если вы повторяете, рекурсируете или выполняете некоторую обработку, используйте локальные переменные.

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

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

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

Ответ 3

Короткий рассказ: если и только если переменная должна быть доступна более чем одним методом (или вне класса), создайте ее как переменную экземпляра. Если вам это нужно только локально, в одном методе это должна быть локальная переменная. Переменные экземпляра являются более дорогостоящими, чем локальные переменные. Имейте в виду: переменные экземпляра инициализируются значениями по умолчанию, а локальные переменные - нет.

Ответ 4

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

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

Ответ 5

Объявлять переменные как можно более узкими. Сначала объявите локальные переменные. Если этого недостаточно, используйте переменные экземпляра. Если этого недостаточно, используйте переменные класса (статические).

Мне нужно вернуть более одного значения, возвращая составную структуру, например массив или объект.

Ответ 6

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

Однако хорошей практикой является использование как можно большего количества локальных переменных. Зачем? Для вашего простого проекта только с одним классом нет никакой разницы. Для проекта, который включает в себя множество классов, есть большая разница. Переменная экземпляра указывает состояние вашего класса. Чем больше переменных экземпляра в вашем классе, тем больше состояний этого класса может быть, а затем, чем сложнее этот класс, тем сложнее класс поддерживается или может быть больше ошибок, связанных с вашим проектом. Поэтому хорошей практикой является использование как можно более локальной переменной, чтобы максимально упростить состояние класса.

Ответ 7

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

Ответ 8

Попробуйте не возвращать более одного значения из ваших методов на первом месте. Если вы не можете, и в некоторых случаях вы действительно не можете, я бы рекомендовал инкапсулировать это в классе. В последнем случае я бы рекомендовал изменить другую переменную внутри вашего класса (переменную экземпляра). Проблема с подходом к переменным экземплярам заключается в том, что он увеличивает побочные эффекты - например, вы вызываете метод A в своей программе и модифицируете некоторые переменные (-ы) экземпляра. Со временем это приводит к усложнению кода, а обслуживание становится все труднее и труднее.

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

Ответ 9

Обычно переменные должны иметь минимальный объем.

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

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

Изобразите базу кода с тысячами таких методов:

private ClassThatHoldsReturnInfo foo(OneReallyBigClassThatHoldsCertainThings big,
 AnotherClassThatDoesLittle little) {
    LocalClassObjectJustUsedHere here;
             ...
}
private ClassThatHoldsReturnInfo bar(OneMediumSizedClassThatHoldsCertainThings medium,
 AnotherClassThatDoesLittle little) {
    ...
}

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

private OneReallyBigClassThatHoldsCertainThings big;
private OneMediumSizedClassThatHoldsCertainThings medium;
private AnotherClassThatDoesLittle little;
private ClassThatHoldsReturnInfo ret;

private void foo() { 
    LocalClassObjectJustUsedHere here; 
    .... 
}
private void bar() { 
    .... 
}

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

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

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

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

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

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

Ответ 10

Используйте переменную экземпляра, когда

  • Если 2 функции в классе нуждаются в одном и том же значении, тогда сделайте его переменной экземпляра ИЛИ
  • Если состояние не ожидается, измените значение переменной экземпляра. например: неизменяемый объект, DTO, LinkedList, те, у которых конечные переменные ИЛИ
  • Если это базовые данные о том, какие действия выполняются. например: final in arr [] в исходном коде PriorityQueue.java ИЛИ
  • Даже если он используется только один раз && ожидается, что состояние изменится, сделайте его экземпляром, если он используется только один раз функцией, список параметров которой должен быть пустым. например: HTTPCookie.java: функция 860 hashcode() использует переменную пути.

Аналогично используйте локальную переменную, если ни одно из этих условий не соответствует, в частности, роль переменной закончится после того, как стек выскочит. например: Comparator.compare(o1, o2);