Java 实例变量与局部变量
我正在上高中的第一堂编程课。我们正在做第一学期末的项目。
本项目只涉及一个类,但是方法很多。我的问题是关于实例变量和局部变量的最佳实践。对我来说,几乎只使用实例变量来编写代码似乎要容易得多。但我不确定这是否是我应该这样做的方式,或者我是否应该更多地使用局部变量(我只需要让方法更多地接受局部变量的值)。
我这样做的原因也是因为很多时候我想让一个方法返回两个或三个值,但这当然是不可能的。因此,简单地使用实例变量似乎更容易,并且不必担心,因为它们在类中是通用的。
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.
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(10)
我还没有看到有人讨论过这个问题,所以我会投入更多的思考。简短的答案/建议是不要仅仅因为您认为实例变量更容易返回值而使用实例变量而不是局部变量。如果您不适当地使用局部变量和实例变量,您的代码将会变得非常非常困难。您将产生一些很难追踪的严重错误。如果您想了解我所说的严重错误是什么意思,以及它可能是什么样子,请继续阅读。
让我们尝试仅使用您建议的实例变量来写入函数。我将创建一个非常简单的类:
我知道这是一个愚蠢的程序,但我们可以用它来说明这样的概念:对此类事情使用实例变量是一个非常糟糕的主意。您会发现最重要的是,这些方法使用了我们拥有的所有实例变量。每次调用时,它都会修改索引、计数器和索引。您会发现的第一个问题是,依次调用这些方法可能会修改先前运行的答案。例如,如果您编写了以下代码:
由于 findColor 使用实例变量来跟踪返回值,因此我们一次只能返回一个结果。让我们在再次调用之前尝试保存对这些结果的引用:
在第二个示例中,我们保存了第三行的红色位置,但同样的事情发生了!?为什么我们丢失了它们?!因为 idea.indexes 被清除而不是分配,所以一次只能使用一个答案。您必须完全使用该结果才能再次调用它。一旦你再次调用一个方法,结果就会被清除,你会失去一切。为了解决这个问题,您必须每次分配一个新结果,以便红色和绿色答案是分开的。因此,让我们克隆我们的答案来创建新的副本:
好吧,最后我们有两个单独的结果。红色和绿色的结果现在是分开的。但是,在程序运行之前,我们必须了解 BadIdea 的内部运作方式,不是吗?我们需要记住每次调用它时都克隆返回值,以安全地确保我们的结果不会被破坏。为什么呼叫者被迫记住这些细节?如果我们不必这样做不是更容易吗?
另请注意,调用者必须使用局部变量来记住结果,因此虽然您没有在 BadIdea 的方法中使用局部变量,但调用者必须使用它们来记住结果。那么你真正完成了什么?您实际上只是将问题转移给呼叫者,迫使他们做更多事情。而且你推给调用者的工作并不是一个容易遵循的规则,因为该规则有很多例外。
现在让我们尝试使用两种不同的方法来做到这一点。请注意,我是如何“聪明”地重用这些相同的实例变量来“节省内存”并保持代码紧凑。 ;-)
同样的事情发生了!该死的,但我是如此“聪明”,节省了内存,而且代码使用了更少的资源!这是使用实例变量的真正危险,就像调用方法现在依赖于顺序一样。如果我改变方法调用的顺序,即使我没有真正改变 BadIdea 的底层状态,结果也会不同。我没有改变地图的内容。为什么当我以不同的顺序调用方法时,程序会产生不同的结果?
与交换这两种方法相比,会产生不同的结果:
这些类型的错误确实很难追踪,尤其是当这些行彼此不相邻时。您只需在这两行之间的任何位置添加一个新调用即可完全破坏您的程序,并获得截然不同的结果。当然,当我们处理少量的行时,很容易发现错误。但是,在较大的程序中,即使程序中的数据没有更改,您也可能会浪费数天时间来尝试重现它们。
这仅考虑单线程问题。如果 BadIdea 在多线程情况下使用,错误可能会变得非常奇怪。如果同时调用 findColors() 和 findOppositeColors() 会发生什么?崩溃,你的头发全部掉光,死亡,时空塌陷成一个奇点,宇宙被吞噬?可能至少有两个。线程现在可能已经超出了你的理解范围,但希望我们现在可以引导你远离做坏事,这样当你真正接触线程时,这些不好的做法不会让你真正心痛。
您是否注意到调用这些方法时必须多么小心?他们互相覆盖,他们可能随机共享内存,你必须记住它在内部如何工作的细节才能使其在外部工作,改变事物被调用的顺序会在下一行中产生非常大的变化,而且它只能在单线程的情况下工作。这样做会产生非常脆弱的代码,每当您触摸它时,它们似乎都会崩溃。我展示的这些做法直接导致了代码的脆弱。
虽然这可能看起来像封装,但事实恰恰相反,因为调用者必须知道如何编写它的技术细节。调用者必须以非常特殊的方式编写代码才能使代码正常工作,并且如果不了解代码的技术细节,他们就无法做到这一点。这通常被称为泄漏抽象,因为该类应该隐藏抽象/接口背后的技术细节,但技术细节泄漏出来,迫使调用者改变其行为。每个解决方案都有一定程度的漏洞,但是使用上述任何技术都可以保证,无论您试图解决什么问题,如果您应用它们,都会出现严重的漏洞。现在让我们看看 GoodIdea。
让我们使用局部变量重写:
这解决了我们上面讨论的每个问题。我知道我没有跟踪计数器或返回它,但如果我这样做了,我可以创建一个新类并返回它而不是列表。有时我使用以下对象来快速返回多个结果:
答案很长,但是一个非常重要的主题。
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:
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:
Since findColor uses instance variables to track returned values we can only return one result at a time. Let's try and save off a reference to those results before we call it again:
In this second example we saved the red positions on the 3rd line, but same thing happened!?Why did we lose them?! Because idea.indexes was cleared instead of allocated so there can only be one answer used at a time. 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:
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? 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. 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. ;-)
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. I didn't change the contents of the map. Why does the program yield different results when I call the methods in different order?
Produces a different result than if I swapped those two methods:
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. What happens if findColors() and findOppositeColors() is called at the same time? 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.
Let's rewrite using local variables:
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. Sometimes I use the following object to return multiple results quickly:
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).
一种简单的方法:如果变量必须由多个方法共享,则使用实例变量,否则使用局部变量。
然而,好的做法是使用尽可能多的局部变量。为什么?对于只有一个类的简单项目来说,没有什么区别。对于包含很多类的项目来说,存在很大的差异。实例变量指示类的状态。类中的实例变量越多,该类可以拥有的状态就越多,然后该类就越复杂,该类的维护就越困难,或者您的项目可能就越容易出错。因此,好的做法是使用尽可能多的局部变量来保持类的状态尽可能简单。
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.
方法内部的局部变量始终是首选,因为您希望使每个变量的范围尽可能小。但如果多个方法需要访问一个变量,那么它就必须是实例变量。
局部变量更像是用于得出结果或动态计算某些内容的中间值。实例变量更像是类的属性,例如您的年龄或姓名。
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.
简而言之:当且仅当一个变量需要由多个方法(或类外部)访问时,才将其创建为实例变量。如果您仅在本地单个方法中需要它,则它必须是局部变量。
实例变量比局部变量更昂贵。
请记住:实例变量被初始化为默认值,而局部变量则不然。
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.
声明变量的范围尽可能窄。首先声明局部变量。如果这还不够,请使用实例变量。如果这还不够,请使用类(静态)变量。
如果您需要返回多个值,则返回一个复合结构,例如数组或对象。
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.
首先,尽量不要从方法中返回多个值。如果你不能,并且在某些情况下你确实不能,那么我建议将其封装在一个类中。在最后一种情况下,我建议更改类中的另一个变量(实例变量)。实例变量方法的问题在于它增加了副作用 - 例如,您在程序中调用方法 A,它会修改一些实例变量。随着时间的推移,这会导致代码的复杂性增加,维护变得越来越困难。
当我必须使用实例变量时,我尝试将 then 设置为 Final 并在类构造函数中初始化 then,以便最大限度地减少副作用。这种编程风格(最大限度地减少应用程序中的状态更改)应该会产生更易于维护的更好代码。
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). 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. 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:
And, on the other hand, imagine a code base with lots of instance variables like this:
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.
时使用实例变量
或
或
或
同样,当这些条件都不匹配时,特别是当变量的作用在堆栈弹出后结束时,请使用局部变量。例如:Comparator.compare(o1, o2);
Use instance variables when
or
or
or
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);