简体   繁体   English

Java 实例变量与局部变量

[英]Java instance variables vs. local variables

I'm in my first programming class in high school.我在高中的第一个编程课上。 We're doing our end of the first semester project.我们正在做我们第一学期末的项目。

This project only involves one class, but many methods.本项目只涉及一个类,方法很多。 My question is about best practice with instance variables and local variables.我的问题是关于实例变量和局部变量的最佳实践。 It seems that it would be much easier for me to code using almost only instance variables.对我来说,几乎只使用实例变量进行编码似乎要容易得多。 But I'm not sure if this is how I should be doing it or if I should be using local variables more (I would just have to have methods take in the values of local variables a lot more).但我不确定这是否是我应该做的,或者我是否应该更多地使用局部变量(我只需要让方法更多地接收局部变量的值)。

My reasoning for this is also because a lot of times I'll want to have a method return two or three values, but this is of course not possible.我的理由也是因为很多时候我希望一个方法返回两个或三个值,但这当然是不可能的。 Thus it just seems easier to simply use instance variables and never having to worry since they are universal in the class.因此,简单地使用实例变量似乎更容易,而不必担心,因为它们在类中是通用的。

I haven't seen anyone discuss this so I'll throw in more food for thought.我还没有看到任何人讨论这个,所以我会提出更多的思考。 The short answer/advice is don't use instance variables over local variables just because you think they are easier to return values.简短的回答/建议是不要仅仅因为您认为它们更容易返回值而在局部变量上使用实例变量。 You are going to make working with your code very very hard if you don't use local variables and instance variables appropriately.如果您没有适当地使用局部变量和实例变量,您将很难使用您的代码。 You will produce some serious bugs that are really hard to track down.你会产生一些很难追踪的严重错误。 If you want to understand what I mean by serious bugs, and what that might look like read on.如果您想了解我所说的严重错误是什么意思,以及它可能是什么样子,请继续阅读。

Let's try and use only instance variables as you suggest to write to functions.让我们尝试只使用您建议的实例变量来写入函数。 I'll create a very simple class:我将创建一个非常简单的类:

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++;
         }
      }
   }
}

This is a silly program I know, but we can use it to illustrate the concept that using instance variables for things like this is a tremendously bad idea.我知道这是一个愚蠢的程序,但我们可以用它来说明这样一个概念,即对这样的事情使用实例变量是一个非常糟糕的主意。 The biggest thing you'll find is that those methods use all of the instance variables we have.您会发现最重要的是这些方法使用了我们拥有的所有实例变量。 And it modifies indexes, counter, and index every time they are called.并且每次调用时都会修改索引、计数器和索引。 The first problem you'll find is that calling those methods one after the other can modify the answers from prior runs.您会发现的第一个问题是,一个接一个地调用这些方法可以修改先前运行的答案。 So for example, if you wrote the following code:例如,如果您编写了以下代码:

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

Since findColor uses instance variables to track returned values we can only return one result at a time.由于 findColor 使用实例变量来跟踪返回值,我们一次只能返回一个结果。 Let's try and save off a reference to those results before we call it again:在再次调用之前,让我们尝试保存对这些结果的引用:

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;

In this second example we saved the red positions on the 3rd line, but same thing happened!?Why did we lose them?!在第二个例子中,我们保存了第 3 行的红色位置,但同样的事情发生了!?为什么我们失去了它们?! Because idea.indexes was cleared instead of allocated so there can only be one answer used at a time.因为idea.indexes 被清除而不是分配,所以一次只能使用一个答案。 You have to completely finish using that result before calling it again.在再次调用它之前,您必须完全使用该结果。 Once you call a method again the results are cleared and you lose everything.一旦你再次调用一个方法,结果就会被清除,你就会失去一切。 In order to fix this you'll have to allocate a new result each time so red and green answers are separate.为了解决这个问题,你每次都必须分配一个新的结果,这样红色和绿色的答案是分开的。 So let's clone our answers to create new copies of things:因此,让我们克隆我们的答案以创建事物的新副本:

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;

Ok finally we have two separate results.好的,最后我们有两个单独的结果。 The results of red and green are now separate.红色和绿色的结果现在是分开的。 But, we had to know a lot about how BadIdea operated internally before the program worked didn't we?但是,在程序运行之前,我们必须非常了解 BadIdea 的内部运作方式,不是吗? We need to remember to clone the returns every time we called it to safely make sure our results didn't get clobbered.我们需要记住每次调用它时都要克隆返回值,以确保我们的结果不会被破坏。 Why is the caller forced to remember these details?为什么来电者被迫记住这些细节? Wouldn't it be easier if we didn't have to do that?如果我们不必这样做,不是更容易吗?

Also notice that the caller has to use local variables to remember the results so while you didn't use local variables in the methods of BadIdea the caller has to use them to remember results.另请注意,调用者必须使用局部变量来记住结果,因此虽然您没有在 BadIdea 的方法中使用局部变量,但调用者必须使用它们来记住结果。 So what did you really accomplish?那么你真正完成了什么? You really just moved the problem to the caller forcing them to do more.你真的只是把问题转移到调用者身上,迫使他们做更多的事情。 And the work you pushed onto the caller is not an easy rule to follow because there are some many exceptions to the rule.你推送给调用者的工作不是一个容易遵循的规则,因为规则有很多例外。

Now let's try doing that with two different methods.现在让我们尝试用两种不同的方法来做到这一点。 Notice how I've been "smart" and I reused those same instance variables to "save memory" and kept the code compact.请注意我是如何“聪明”的,我重用了那些相同的实例变量来“节省内存”并保持代码紧凑。 ;-) ;-)

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;

Same thing happened!同样的事情发生了! Damn but I was being so "smart" and saving memory and the code uses less resources!!!该死,但我太“聪明”了,节省了内存,代码使用的资源更少!!! This is the real peril of using instance variables like this is calling methods is order dependent now.这是使用实例变量的真正危险,因为现在调用方法依赖于顺序。 If I change the order of the method calls the results are different even though I haven't really changed the underlying state of BadIdea.如果我改变方法调用的顺序,即使我没有真正改变 BadIdea 的底层状态,结果也会有所不同。 I didn't change the contents of the map.我没有改变地图的内容。 Why does the program yield different results when I call the methods in different order?当我以不同的顺序调用方法时,为什么程序会产生不同的结果?

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

Produces a different result than if I swapped those two methods:产生与交换这两种方法不同的结果:

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

These types of errors are really hard to track down especially when those lines aren't right next to each other.这些类型的错误真的很难追踪,尤其是当这些线彼此不相邻时。 You can completely break your program by just adding a new call in anywhere between those two lines and get wildly different results.你可以通过在这两行之间的任何地方添加一个新的调用来完全破坏你的程序,并得到截然不同的结果。 Sure when we're dealing with small number of lines it's easy to spot errors.当然,当我们处理少量行时,很容易发现错误。 But, in a larger program you can waste days trying to reproduce them even though the data in the program hasn't changed.但是,在更大的程序中,即使程序中的数据没有改变,您也可能会浪费数天时间尝试重现它们。

And this only looks at single threaded problems.而这只关注单线程问题。 If BadIdea was being used in a multi-threaded situation the errors can get really bizarre.如果在多线程情况下使用 BadIdea,错误可能会变得非常奇怪。 What happens if findColors() and findOppositeColors() is called at the same time?如果同时调用 findColors() 和 findOppositeColors() 会发生什么? Crash, all your hair falls out, Death, space and time collapse into a singularity and the universe is swallows up?崩溃,你的头发都掉光了,死亡,时空坍缩成一个奇点,宇宙被吞没? Probably at least two of those.可能至少有两个。 Threads are probably above your head now, but hopefully we can steer you away from doing bad things now so when you do get to threads those bad practices don't cause you real heartache.线程现在可能在您的头顶之上,但希望我们现在可以引导您远离做坏事,这样当您进入线程时,这些不良做法不会让您真正心痛。

Did you notice how careful you had to be when calling the methods?您是否注意到在调用方法时必须非常小心? They overwrote each other, they shared memory possibly randomly, you had to remember the details of how it worked on the inside to make it work on the outside, changing the order in which things were called produce very big changes in the next lines down, and it only could only work in a single thread situation.它们相互覆盖,它们可能随机地共享内存,你必须记住它在内部如何工作的细节,以使其在外部工作,改变事物被调用的顺序在下一行产生非常大的变化,并且只能在单线程情况下工作。 Doing things like this will produce really brittle code that seems to fall apart whenever you touch it.做这样的事情会产生非常脆弱的代码,当你触摸它时似乎会崩溃。 These practices I showed contributed directly to the code being brittle.我展示的这些实践直接导致代码变得脆弱。

While this might look like encapsulation it is the exact opposite because the technical details of how you wrote it have to be known to the caller .虽然这可能看起来像封装,但恰恰相反,因为调用者必须知道如何编写它的技术细节 The caller has to write their code in a very particular way to make their code work, and they can't do it without knowing about the technical details of your code.调用者必须以非常特殊的方式编写他们的代码才能使他们的代码工作,如果不了解代码的技术细节,他们就无法做到。 This is often called a Leaky Abstraction because the class is suppose to hide the technical details behind an abstraction/interface, but the technical details leak out forcing the caller to change their behavior.这通常被称为泄漏抽象,因为该类假设隐藏抽象/接口背后的技术细节,但技术细节泄漏迫使调用者改变他们的行为。 Every solution has some degree of leaky-ness, but using any of the above techniques like these guarantees no matter what problem you are trying to solve it will be terribly leaky if you apply them.每个解决方案都有一定程度的泄漏,但是使用上述任何一种技术,无论您尝试解决什么问题,都可以保证如果您应用它们,就会非常泄漏。 So let's look at the GoodIdea now.现在让我们来看看GoodIdea。

Let's rewrite using local variables:让我们使用局部变量重写:

 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;
   }
 }

This fixes every problem we discussed above.这解决了我们上面讨论的每个问题。 I know I'm not keeping track of counter or returning it, but if I did I can create a new class and return that instead of List.我知道我没有跟踪计数器或返回它,但是如果我这样做了,我可以创建一个新类并返回它而不是 List。 Sometimes I use the following object to return multiple results quickly:有时我使用以下对象快速返回多个结果:

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

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

Long answer, but a very important topic.答案很长,但一个非常重要的话题。

Use instance variables when it's a core concept of your class.当实例变量是类的核心概念时,请使用它。 If you're iterating, recursing or doing some processing, then use local variables.如果您正在迭代、递归或进行一些处理,请使用局部变量。

When you need to use two (or more) variables in the same places, it's time to create a new class with those attributes (and appropriate means to set them).当您需要在同一位置使用两个(或更多)变量时,是时候创建一个具有这些属性的新类(以及设置它们的适当方法)。 This will make your code cleaner and help you think about problems (each class is a new term in your vocabulary).这将使您的代码更清晰并帮助您思考问题(每个类都是您词汇表中的一个新术语)。

One variable may be made a class when it is a core concept.当一个变量是一个核心概念时,它可以成为一个类。 For example real-world identifiers: these could be represented as Strings, but often, if you encapsulate them into their own object they suddenly start "attracting" functionality (validation, association to other objects, etc.)例如现实世界的标识符:这些可以表示为字符串,但通常,如果您将它们封装到自己的对象中,它们会突然开始“吸引”功能(验证、与其他对象的关联等)

Also (not entirely related) is object consistency - an object is able to ensure that its state makes sense.此外(不完全相关)对象一致性 - 对象能够确保其状态有意义。 Setting one property may alter another.设置一个属性可能会改变另一个。 It also makes it far easier to alter your program to be thread-safe later (if required).它还可以更轻松地稍后将程序更改为线程安全(如果需要)。

Short story: if and only if a variable needs to be accessed by more than one method (or outside of the class), create it as an instance variables.小故事:当且仅当需要通过多个方法(或在类之外)访问变量时,将其创建为实例变量。 If you need it only locally, in a single method, it has to be a local variable.如果您只在本地需要它,在单个方法中,它必须是一个局部变量。

Instance variables are more costly than local variables.实例变量比局部变量成本更高。

Keep in mind: instance variables are initialized to default values while local variables are not.请记住:实例变量被初始化为默认值,而局部变量则不是。

Local variables internal to methods are always prefered, since you want to keep each variable's scope as small as possible.始终首选方法内部的局部变量,因为您希望使每个变量的范围尽可能小。 But if more than one method needs to access a variable, then it's going to have to be an instance variable.但是如果多个方法需要访问一个变量,那么它就必须是一个实例变量。

Local variables are more like intermediate values used to reach a result or compute something on the fly.局部变量更像是用于达到结果或即时计算某些东西的中间值。 Instance variables are more like attributes of a class, like your age or name.实例变量更像是一个类的属性,比如你的年龄或名字。

The easy way: if the variable must be shared by more than one method, use instance variable, otherwise use local variable.简单的方法:如果变量必须由多个方法共享,则使用实例变量,否则使用局部变量。

However, the good practice is to use as more local variables as possible.但是,好的做法是使用尽可能多的局部变量。 Why?为什么? For your simple project with only one class, there is no difference.对于只有一个类的简单项目,没有区别。 For a project that includes a lot of classes, there is big difference.对于一个包含很多类的项目,差别很大。 The instance variable indicates the state of your class.实例变量指示类的状态。 The more instance variables in your class, the more states this class can have and then, the more complex this class is, the hard the class is maintained or the more error prone your project might be.你的类中的实例变量越多,这个类可以拥有的状态就越多,然后这个类越复杂,这个类就越难维护,或者你的项目越容易出错。 So the good practice is to use as more local variable as possible to keep the state of the class as simple as possible.所以好的做法是使用尽可能多的局部变量来保持类的状态尽可能简单。

Declare variables to be scoped as narrowly as possible.声明变量的范围尽可能窄。 Declare local variables first.先声明局部变量。 If this isn't sufficient, use instance variables.如果这还不够,请使用实例变量。 If this isn't sufficient, use class (static) variables.如果这还不够,请使用类(静态)变量。

I you need to return more than one value return a composite structure, like an array or an object.我需要返回多个值返回一个复合结构,如数组或对象。

Try to think about your problem in terms of objects.试着从对象的角度考虑你的问题。 Each class represents a different type of object.每个类代表不同类型的对象。 Instance variables are the pieces of data that a class needs to remember in order to work, either with itself or with other objects.实例变量是类需要记住以便与自身或其他对象一起工作的数据片段。 Local variables should just be used intermediate calculations, data that you don't need to save once you leave the method.局部变量应该只用于中间计算,离开方法后不需要保存的数据。

Try not to return more than one value from your methods in first place.首先尽量不要从您的方法中返回多个值。 If you can't, and in some cases you really can't, then I would recommend encapsulating that in a class.如果你不能,在某些情况下你真的不能,那么我建议将它封装在一个类中。 Just in last case I would recommend changing another variable inside your class (an instance variable).在最后一种情况下,我建议更改类中的另一个变量(实例变量)。 The problem with the instance variables approach is that it increases side effects - for example, you call method A in your program and it modifies some instance(s) variable(s).实例变量方法的问题在于它增加了副作用 - 例如,您在程序中调用方法 A 并且它修改了一些实例变量。 Over time, that leads to increased complexity in your code and maintenance becomes harder and harder.随着时间的推移,这会导致代码的复杂性增加,并且维护变得越来越困难。

When I have to use instance variables, I try to make then final and initialize then in the class constructors, so side effects are minimized.当我必须使用实例变量时,我尝试在类构造函数中使 then 成为 final 并初始化 then,因此副作用被最小化。 This programming style (minimizing the state changes in your application) should lead to better code that is easier to maintain.这种编程风格(最小化应用程序中的状态更改)应该会产生更易于维护的更好的代码。

Generally variables should have minimal scope.通常变量应该有最小的范围。

Unfortunately, in order to build classes with minimized variable scope, one often needs to do a lot of method parameter passing.不幸的是,为了构建具有最小变量范围的类,通常需要进行大量的方法参数传递。

But if you follow that advice all the time, perfectly minimizing variable scope, you may end up with a lot of redundancy and method inflexibility with all the required objects passed in and out of methods.但是如果你一直遵循这个建议,完美地最小化变量范围,你最终可能会因为所有必需的对象传入和传出方法而导致大量冗余和方法不灵活。

Picture a code base with thousands of methods like this:想象一个包含数千种方法的代码库,如下所示:

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

And, on the other hand, imagine a code base with lots of instance variables like this:并且,另一方面,想象一个包含大量实例变量的代码库,如下所示:

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

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

As code increases, the first way may minimize variable scope best, but can easily lead to a lot of method parameters being passed around.随着代码的增加,第一种方式可能最好地最小化变量范围,但很容易导致大量方法参数被传递。 The code will usually be more verbose and this can lead to a complexity as one refactors all these methods.代码通常会更加冗长,这会导致重构所有这些方法时的复杂性。

Using more instance variables can reduce the complexity of lots of method parameters being passed around and can give a flexibility to methods when you are frequently reorganizing methods for clarity.使用更多的实例变量可以降低传递的大量方法参数的复杂性,并且可以在您经常重新组织方法以保持清晰时为方法提供灵活性。 But it creates more object state that you have to maintain.但它会创建更多您必须维护的对象状态。 Generally the advice is to do the former and refrain from the latter.一般来说,建议是做前者而不要做后者。

However, very often, and it may depend on the person, one can more easily manage state complexity compared with the thousands of extra object references of the first case.然而,很多时候,这可能取决于个人,与第一种情况的数千个额外对象引用相比,人们可以更容易地管理状态复杂性。 One may notice this when business logic within methods increases and organization needs to change to keep order and clarity.当方法中的业务逻辑增加并且组织需要改变以保持秩序和清晰时,人们可能会注意到这一点。

Not only that.不仅如此。 When you reorganize your methods to keep clarity and make lots of method parameter changes in the process, you end up with lots of version control diffs which is not so good for stable production quality code.当您重新组织您的方法以保持清晰并在过程中进行大量方法参数更改时,您最终会得到许多版本控制差异,这对于稳定的生产质量代码来说并不是那么好。 There is a balance.有一个平衡。 One way causes one kind of complexity.一种方式导致一种复杂性。 The other way causes another kind of complexity.另一种方式导致另一种复杂性。

Use the way that works best for you.使用最适合您的方式。 You will find that balance over time.随着时间的推移,你会发现这种平衡。

I think this young programmer has some insightful first impressions for low maintenance code.我认为这位年轻的程序员对低维护代码有一些深刻的第一印象。

Use instance variables when使用实例变量时

  1. If two functions in the class need the same value, then make it an instance variable or如果类中的两个函数需要相同的值,则将其设为实例变量
  2. If the state is not expected to change, make it an instance variable.如果预期状态不会改变,请将其设为实例变量。 For example: immutable object, DTO , LinkedList, those with final variables or例如:不可变对象、 DTO 、LinkedList、带有最终变量的对象
  3. If it is an underlying data on whom actions are performed.如果它是执行操作的基础数据。 For example: final in arr[] in the PriorityQueue.java source code file or例如: PriorityQueue.java源代码文件中的arr[]中的final
  4. Even if it is used only once and state is expected to change, make it an instance if it is used only once by a function whose parameter list should be empty.即使它只被使用一次并且状态预计会改变,如果它只被一个参数列表应该为空的函数使用一次,那么让它成为一个实例。 For example: HTTPCookie.java Line: 860 hashcode() function uses 'path variable'.例如: HTTPCookie.java 行:860 hashcode() 函数使用“路径变量”。

Similarly, use a local variable when none of these conditions match, specifically if the role of the variable would end after the stack is popped off.类似地,当这些条件都不匹配时,使用局部变量,特别是当堆栈弹出后变量的作用将结束时。 For example: Comparator.compare(o1, o2);例如: Comparator.compare(o1, o2);

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM