有人可以向我解释一下传递“值”背后的原因是什么吗? 而不是通过“参考” 在Java中是?
我对 Java 相当陌生(多年来一直在写其他东西),除非我遗漏了一些东西(并且我很高兴在这里犯了错误),否则以下是一个致命的缺陷......
String foo = new String();
thisDoesntWork(foo);
System.out.println(foo);//this prints nothing
public static void thisDoesntWork(String foo){
foo = "howdy";
}
现在,我很清楚(措辞相当糟糕的)概念是,在 java 中,所有内容都是通过“值”而不是“引用”传递的,但是 String 是一个对象,并且具有各种附加功能,因此,人们会期望与 int 不同,用户会能够对传递到方法中的内容进行操作(并且不会被重载 = 设置的值所困扰)。
有人可以向我解释一下这个设计选择背后的原因是什么吗? 正如我所说,我不想在这里,也许我错过了一些明显的东西?
I'm fairly new to Java (been writing other stuff for many years) and unless I'm missing something (and I'm happy to be wrong here) the following is a fatal flaw...
String foo = new String();
thisDoesntWork(foo);
System.out.println(foo);//this prints nothing
public static void thisDoesntWork(String foo){
foo = "howdy";
}
Now, I'm well aware of the (fairly poorly worded) concept that in java everything is passed by "value" and not "reference", but String is an object and has all sorts of bells and whistles, so, one would expect that unlike an int a user would be able to operate on the thing that's passed into the method (and not be stuck with the value set by the overloaded =).
Can someone explain to me what the reasoning behind this design choice was? As I said, I'm not looking to be right here, and perhaps I'm missing something obvious?
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(15)
这篇咆哮比我尝试的更好地解释了这一点:
This rant explains it better than I could ever even try to:
当您传递“foo”时,您将“foo”的引用作为值传递给ThisDoesntWork()。 这意味着当您在方法内部对“foo”进行赋值时,您只是将局部变量 (foo) 的引用设置为对新字符串的引用。
在考虑字符串在 Java 中的行为时要记住的另一件事是字符串是不可变的。 它在 C# 中的工作方式相同,并且有一些很好的原因:
现在谈谈你更大的问题。 为什么对象以这种方式传递? 好吧,如果 Java 将字符串作为传统上所说的“按值”传递,那么它必须在将字符串传递给函数之前实际复制整个字符串。 那是相当慢的。 如果它通过引用传递字符串并让您更改它(就像 C 那样),您就会遇到我刚才列出的问题。
When you pass "foo", you're passing the reference to "foo" as a value to ThisDoesntWork(). That means that when you do the assignment to "foo" inside of your method, you are merely setting a local variable (foo)'s reference to be a reference to your new string.
Another thing to keep in mind when thinking about how strings behave in Java is that strings are immutable. It works the same way in C#, and for some good reasons:
Now onto your bigger question. Why are objects passed this way? Well, if Java passed your string as what you'd traditionally call "by value", it would have to actually copy the entire string before passing it to your function. That's quite slow. If it passed the string by reference and let you change it (like C does), you'd have the problems I just listed.
由于我最初的答案是“为什么会发生这种情况”,而不是“为什么语言被设计成会发生这种情况”,所以我会再试一次。
为了简化事情,我将摆脱方法调用并以另一种方式显示正在发生的情况。
为了让最后一个语句打印“hello”,b必须指向内存中与a指向的同一个“洞”(指针)。 当您想要通过引用传递时,这就是您想要的。 Java 决定不朝这个方向发展有几个原因:
指针令人困惑Java 的设计者试图消除其他语言中一些更令人困惑的东西。 指针与运算符重载一样,是 C/C++ 中最容易被误解和使用不当的结构之一。
指针存在安全风险 误用指针会导致许多安全问题。 恶意程序将某些内容分配给该部分内存,然后您认为是您的对象实际上是其他人的。 (Java 已经通过检查数组消除了最大的安全问题,即缓冲区溢出)
抽象泄漏当您开始准确地处理“内存中的内容和位置”时,您的抽象就不再是一个抽象的概念。抽象。 虽然抽象泄漏几乎肯定会渗透到语言中,但设计者并不想直接将其融入其中。
你关心的只是对象在Java中,一切都是对象,而不是对象占用的空间。 不过,添加指针会使对象占用的空间变得重要......
您可以通过创建“孔”对象来模拟您想要的内容。 您甚至可以使用泛型来使其类型安全。 例如:
Since my original answer was "Why it happened" and not "Why was the language designed so it happened," I'll give this another go.
To simplify things, I'll get rid of the method call and show what is happening in another way.
To get the last statement to print "hello", b has to point to the same "hole" in memory that a points to (a pointer). This is what you want when you want pass by reference. There are a couple of reasons Java decided not to go this direction:
Pointers are Confusing The designers of Java tried to remove some of the more confusing things about other languages. Pointers are one of the most misunderstood and improperly used constructs of C/C++ along with operator overloading.
Pointers are Security Risks Pointers cause many security problems when misused. A malicious program assigns something to that part of memory, then what you thought was your object is actually someone else's. (Java already got rid of the biggest security problem, buffer overflows, with checked arrays)
Abstraction Leakage When you start dealing with "What's in memory and where" exactly, your abstraction becomes less of an abstraction. While abstraction leakage almost certainly creeps into a language, the designers didn't want to bake it in directly.
Objects Are All You Care About In Java, everything is an object, not the space an object occupies. Adding pointers would make the space an object occupies importantant, though.......
You could emulate what you want by creating a "Hole" object. You could even use generics to make it type safe. For example:
您所提出的问题实际上与按值传递、按引用传递或字符串不可变这一事实无关(正如其他人所说)。
在该方法内部,您实际上创建了一个局部变量(我将其称为“localFoo”),它指向与原始变量(“originalFoo”)相同的引用。
当您将“howdy”分配给localFoo时,您不会更改originalFoo指向的位置。
如果你做了类似的事情:
你会期望:
打印出“howdy”吗? 它打印出“”。
您不能通过更改 localFoo 指向的内容来更改 OriginalFoo 指向的内容。 您可以修改两者都指向的对象(如果它不是不可变的)。 例如,
Your question as asked doesn't really have to do with passing by value, passing by reference, or the fact that strings are immutable (as others have stated).
Inside the method, you actually create a local variable (I'll call that one "localFoo") that points to the same reference as your original variable ("originalFoo").
When you assign "howdy" to localFoo, you don't change where originalFoo is pointing.
If you did something like:
Would you expect:
to print out "howdy" ? It prints out "".
You can't change what originalFoo points to by changing what localFoo points to. You can modify the object that both point to (if it wasn't immutable). For example,
在java中,所有传递的变量实际上都是通过值传递的,甚至是对象。 传递给方法的所有变量实际上都是原始值的副本。 在您的字符串示例中,原始指针(它实际上是一个引用 - 但为了避免混淆,我将使用不同的单词)被复制到一个新变量中,该变量成为该方法的参数。
如果一切都通过参考,那就很痛苦了。 人们需要在各处制作私人副本,这绝对是一件非常痛苦的事情。 每个人都知道,使用值类型的不变性等会使您的程序变得更加简单且更具可扩展性。
一些好处包括:
- 无需制作防御副本。
- 线程安全 - 无需担心锁定,以防其他人想要更改对象。
In java all variables passed are actually passed around by value- even objects. All variables passed to a method are actually copies of the original value. In the case of your string example the original pointer ( its actually a reference - but to avoid confusion ill use a different word ) is copied into a new variable which becomes the parameter to the method.
It would be a pain if everything was by reference. One would need to make private copies all over the place which would definitely be a real pain. Everybody knows that using immutability for value types etc makes your programs infinitely simpler and more scalable.
Some benefits include:
- No need to make defensive copies.
- Threadsafe - no need to worry about locking just in case someone else wants to change the object.
问题是您正在实例化 Java 引用类型。 然后将该引用类型传递给静态方法,并将其重新分配给本地范围的变量。
它与不变性无关。 对于可变引用类型,也会发生完全相同的事情。
The problem is you are instantiating a Java reference type. Then you pass that reference type to a static method, and reassign it to a locally scoped variable.
It has nothing to do with immutability. Exactly the same thing would have happened for a mutable reference type.
如果我们对 C 和汇编程序做一个粗略的类比:
Java 中所有内容都按值传递的一个可能原因是,其语言设计人员希望简化语言并以 OOP 方式完成所有操作。
他们宁愿让你使用对象设计一个整数交换器,而不是为按引用传递提供一流的支持,对于委托也是如此(高斯林对函数指针感到讨厌,他宁愿将该功能塞到对象中)和枚举。
他们过度简化(一切都是对象)语言,从而损害了对大多数语言结构没有一流的支持,例如通过引用传递、委托、枚举、属性。
If we would make a rough C and assembler analogy:
One possible reason why everything is passed by value in Java, its language designer folks want to simplify the language and make everything done in OOP manner.
They would rather have you design an integer swapper using objects than them provide a first class support for by-reference passing, the same for delegate(Gosling feels icky with pointer to function, he would rather cram that functionality to objects) and enum.
They over-simplify(everything is object) the language to the detriment of not having first class support for most language constructs, e.g. passing by reference, delegates, enum, properties comes to mind.
你确定它打印的是 null 吗? 我认为它只是空白,因为当您初始化 foo 变量时,您提供了空字符串。
在 thisDoesntWork 方法中对 foo 进行赋值不会更改类中定义的 foo 变量的引用,因此 System.out.println(foo) 中的 foo 仍将指向旧的空字符串对象。
Are you sure it prints null? I think it will be just blank as when you initialized the foo variable you provided empty String.
The assigning of foo in thisDoesntWork method is not changing the reference of the foo variable defined in class so the foo in System.out.println(foo) will still point to the old empty string object.
戴夫,你必须原谅我(好吧,我想你不必“必须”,但我宁愿你这样做),但这个解释并不太令人信服。 安全性收益相当小,因为任何需要更改字符串值的人都会找到一种通过一些丑陋的解决方法来实现的方法。 还有速度?! 您自己(非常正确)断言,整个+业务非常昂贵。
你们其他人,请理解我明白它是如何工作的,我问为什么它会这样工作......请停止解释方法之间的差异。
(老实说,我并不是在这里寻求任何形式的战斗,顺便说一句,我只是不明白这是一个理性的决定)。
Dave, you have to forgive me (well, I guess you don't "have to", but I'd rather you did) but that explanation is not overly convincing. The Security gains are fairly minimal since anyone who needs to change the value of the string will find a way to do it with some ugly workaround. And speed?! You yourself (quite correctly) assert that the whole business with the + is extremely expensive.
The rest of you guys, please understand that I GET how it works, I'm asking WHY it works that way... please stop explaining the difference between the methodologies.
(and I honestly am not looking for any sort of fight here, btw, I just don't see how this was a rational decision).
@Axelle
Mate你真的知道按值传递和按引用传递之间的区别吗?
在java中,甚至引用也是按值传递的。 当您传递对对象的引用时,您将在第二个变量中获得引用指针的副本。 这就是为什么可以更改第二个变量而不影响第一个变量。
@Axelle
Mate do you really know the difference between passing by value and by reference ?
In java even references are passed by value. When you pass a reference to an object you are getting a copy of the reference pointer in the second variable. Tahts why the second variable can be changed without affecting the first.
这是因为,它在方法内部创建了一个局部变量。 一个简单的方法(我很确定会起作用)是:
It is because, it creates a local variable inside the method. what would be an easy way (which I'm pretty sure would work) would be:
如果您将对象视为对象中的字段,那么在 Java 中对象是通过引用传递的,因为方法可以修改参数的字段,并且调用者可以观察到修改。 但是,如果您也将对象视为其标识,那么对象将按值传递,因为方法无法以调用者可以观察到的方式更改参数的标识。 所以我想说Java是按值传递的。
If you think of an object as just the fields in the object then objects are passed by reference in Java because a method can modify the fields of a parameter and a caller can observe the modification. However, if you also think of an object as it's identity then objects are passed by value because a method can't change the identity of a parameter in a way that the caller can observe. So I would say Java is pass-by-value.
这是因为在“thisDoesntWork”内部,您实际上破坏了 foo 的本地值。 如果你想以这种方式通过引用传递,总是可以将 String 封装在另一个对象中,比如数组中。
产生以下输出:
This is because inside "thisDoesntWork", you are effectively destroying the local value of foo. If you want to pass by reference in this way, can always encapsulate the String inside another object, say in an array.
Results in the following output:
引用类型参数作为对对象本身的引用传递(而不是对引用对象的其他变量的引用)。 您可以调用已传递的对象上的方法。 但是,在您的代码示例中:
您仅将对字符串
"howdy"
的引用存储在该方法的本地变量中。 调用该方法时,该局部变量 (foo
) 被初始化为调用方的foo
值,但没有对调用方变量本身的引用。 初始化之后:在方法中赋值之后:
您还有另一个问题:
String
实例是不可变的(出于安全考虑而设计),因此您无法修改其值。如果您确实希望您的方法为字符串提供初始值(或者在其生命周期中的任何时间),那么让您的方法返回一个
String
您在调用时分配给调用者变量的值。 像这样的事情,例如:Reference typed arguments are passed as references to objects themselves (not references to other variables that refer to objects). You can call methods on the object that has been passed. However, in your code sample:
you are only storing a reference to the string
"howdy"
in a variable that is local to the method. That local variable (foo
) was initialized to the value of the caller'sfoo
when the method was called, but has no reference to the caller's variable itself. After initialization:After the assignment in your method:
You have another issues there:
String
instances are immutable (by design, for security) so you can't modify its value.If you really want your method to provide an initial value for your string (or at any time in its life, for that matter), then have your method return a
String
value which you assign to the caller's variable at the point of the call. Something like this, for example:去suns网站上做一个非常大的教程。
您似乎不了解变量范围的差异。 “foo”对于您的方法来说是本地的。 该方法之外的任何内容都不能改变“foo”所指向的内容。 引用您的方法的“foo”是一个完全不同的字段 - 它是您的封闭类中的静态字段。
范围界定尤其重要,因为您不希望系统中的所有其他内容都可见。
Go do the really big tutorial on suns website.
You seem not to understand the difference scopes variables can be. "foo" is local to your method. Nothing outside of that method can change what "foo" points too. The "foo" being referred to your method is a completely different field - its a static field on your enclosing class.
Scoping is especially important as you dont want everything to be visible to everything else in your system.