2009-11-25 4 views
22

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

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

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

+0

Можно ли определить другие классы? –

ответ

4

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

8

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

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

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

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

3

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

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

1

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

2

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

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

2

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

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

1

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

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

33

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

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

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; 

В этом втором примере мы сохранили красные позиции на 3-й линии, но то же самое случилось Почему мы теряем их ?! Поскольку 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; 
    } 
} 

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

0

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

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

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

Изображение код базы с тысячами способов, как это:

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() { 
    .... 
} 

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

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

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

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

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

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

0

Использование переменной экземпляра, когда

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

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

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