有人能向我解释一下 Java 中通过“值”而不是“引用”传递的原因是什么吗?
-
21-08-2019 - |
题
我对 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 不同,用户能够对传递到方法中的内容进行操作(并且不会被重载 = 设置的值所困扰)。
有人可以向我解释一下这个设计选择背后的原因是什么吗?正如我所说,我不想在这里,也许我错过了一些明显的东西?
解决方案
这咆哮解释它比我所能,甚至尝试:
在Java中,基元值传递。然而,对象是不 按引用传递。正确的说法是对象引用 通过值被传递。
其他提示
当你传递“foo”时,你就传递了 参考 将“foo”作为 ThisDoesntWork() 的值。这意味着当您在方法内部对“foo”进行赋值时,您只是将局部变量 (foo) 的引用设置为对新字符串的引用。
在考虑字符串在 Java 中的行为时要记住的另一件事是字符串是不可变的。它在 C# 中的工作方式相同,并且有一些充分的理由:
- 安全: :如果没有人可以修改它,那么没有人可以将数据塞入您的字符串并导致缓冲区溢出错误!
- 速度 :如果您可以确定字符串是不可变的,那么您就知道它的大小始终相同,并且在操作它时不必在内存中移动数据结构。您(语言设计者)也不必担心将字符串实现为慢速链表。但这是双向的。仅使用 + 运算符附加字符串可能会占用大量内存,并且您必须使用 StringBuilder 对象以高性能、内存高效的方式执行此操作。
现在谈谈你更大的问题。为什么对象以这种方式传递?好吧,如果 Java 将字符串作为传统上所说的“按值”传递,那么它必须在将字符串传递给函数之前实际复制整个字符串。那是相当慢的。如果它通过引用传递字符串并让您更改它(就像 C 那样),您就会遇到我刚才列出的问题。
由于我最初的答案是“为什么会发生这种情况”,而不是“为什么语言被设计成会发生这种情况”,所以我会再试一次。
为了简化事情,我将摆脱方法调用并以另一种方式显示正在发生的情况。
String a = "hello";
String b = a;
String b = "howdy"
System.out.print(a) //prints hello
要让最后一条语句打印“hello”, 乙 必须指向内存中相同的“洞” A 指向(指针)。当您想要通过引用传递时,这就是您想要的。Java 决定不朝这个方向发展有几个原因:
指针令人困惑 Java 的设计者试图消除其他语言中一些更令人困惑的东西。指针与运算符重载一样,是最容易被误解和使用不当的 C/C++ 结构之一。
指针存在安全风险 误用指针会导致许多安全问题。恶意程序将某些内容分配给该部分内存,然后您认为是您的对象实际上是其他人的。(Java已经摆脱了最大的安全问题,缓冲区溢出,检查数组)
抽象泄漏 当你开始准确地处理“内存中有什么以及在哪里”时,你的抽象就变得不那么抽象了。虽然抽象泄漏几乎肯定会渗透到语言中,但设计者并不想直接将其融入其中。
你关心的只是对象 在Java中,一切都是对象,而不是对象所占据的空间。添加指针会使对象占用的空间变得重要,但是......
您可以通过创建“孔”对象来模拟您想要的内容。您甚至可以使用泛型来使其类型安全。例如:
public class Hole<T> {
private T objectInHole;
public void putInHole(T object) {
this.objectInHole = object;
}
public T getOutOfHole() {
return objectInHole;
}
public String toString() {
return objectInHole.toString();
}
.....equals, hashCode, etc.
}
Hole<String> foo = new Hole<String)();
foo.putInHole(new String());
System.out.println(foo); //this prints nothing
thisWorks(foo);
System.out.println(foo);//this prints howdy
public static void thisWorks(Hole<String> foo){
foo.putInHole("howdy");
}
您的要求的的问题并没有真正有路过的价值做,通过引用传递,或事实上,字符串是不可变(如其他人说明)。
里面的方法,实际上创建一个局部变量(我称之为一个“localFoo”)指向同一基准原来的变量(“originalFoo”)。
当您指定“你好”,以localFoo,你不要在那里originalFoo指向改变。
如果你不喜欢的东西:
String a = "";
String b = a;
String b = "howdy"?
你所期望的:
System.out.print(a)
要打印的“你好”?它打印出 “”。
您不能改变什么改变什么localFoo点originalFoo点。您可以修改的对象都指向(如果不是一成不变的)。例如,
List foo = new ArrayList();
System.out.println(foo.size());//this prints 0
thisDoesntWork(foo);
System.out.println(foo.size());//this prints 1
public static void thisDoesntWork(List foo){
foo.add(new Object);
}
在java的传递所有变量由值 - 即使对象被实际传递周围。传递给方法的所有变量实际上是原始值的副本。在您的字符串例如原始指针(其实际上是一个参考 - 但是为了避免混淆生病使用不同的字)的情况下被复制到在成为参数的方法的新的变量
。这将是一个痛苦,如果一切都通过引用。一个需要传抄遍这肯定会是一个真正的痛苦的地方。大家都知道,使用值类型等不变性,使你的程序无限更简单,更可扩展性。
一些好处包括: - 无需进行防守副本。 - 线程安全 - 没有必要担心锁定,以防其他人想要改变对象
的问题是要实例化一个Java引用类型。然后,你那引用类型传递给一个静态方法,并且重新分配给本地范围的变量。
它无关不变性。完全一样的东西都不会发生了可变引用类型。强>
如果我们做一个粗略的C语言和汇编的比喻:
void Main()
{
// stack memory address of message is 0x8001. memory address of Hello is 0x0001.
string message = "Hello";
// assembly equivalent of: message = "Hello";
// [0x8001] = 0x0001
// message's stack memory address
printf("%d", &message); // 0x8001
printf("%d", message); // memory pointed to of message(0x8001): 0x0001
PassStringByValue(message); // pass the pointer pointed to of message. 0x0001, not 0x8001
printf("%d", message); // memory pointed to of message(0x8001): 0x0001. still the same
// message's stack memory address doesn't change
printf("%d", &message); // 0x8001
}
void PassStringByValue(string foo)
{
printf("%d", &foo); // &foo contains foo's *stack* address (0x4001)
// foo(0x4001) contains the memory pointed to of message, 0x0001
printf("%d", foo); // 0x0001
// World is in memory address 0x0002
foo = "World"; // on foo's memory address (0x4001), change the memory it pointed to, 0x0002
// assembly equivalent of: foo = "World":
// [0x4001] = 0x0002
// print the new memory pointed by foo
printf("%d", foo); // 0x0002
// Conclusion: Not in any way 0x8001 was involved in this function. Hence you cannot change the Main's message value.
// foo = "World" is same as [0x4001] = 0x0002
}
void Main()
{
// stack memory address of message is 0x8001. memory address of Hello is 0x0001.
string message = "Hello";
// assembly equivalent of: message = "Hello";
// [0x8001] = 0x0001
// message's stack memory address
printf("%d", &message); // 0x8001
printf("%d", message); // memory pointed to of message(0x8001): 0x0001
PassStringByRef(ref message); // pass the stack memory address of message. 0x8001, not 0x0001
printf("%d", message); // memory pointed to of message(0x8001): 0x0002. was changed
// message's stack memory address doesn't change
printf("%d", &message); // 0x8001
}
void PassStringByRef(ref string foo)
{
printf("%d", &foo); // &foo contains foo's *stack* address (0x4001)
// foo(0x4001) contains the address of message(0x8001)
printf("%d", foo); // 0x8001
// World is in memory address 0x0002
foo = "World"; // on message's memory address (0x8001), change the memory it pointed to, 0x0002
// assembly equivalent of: foo = "World":
// [0x8001] = 0x0002;
// print the new memory pointed to of message
printf("%d", foo); // 0x0002
// Conclusion: 0x8001 was involved in this function. Hence you can change the Main's message value.
// foo = "World" is same as [0x8001] = 0x0002
}
为什么每件事都要用在Java中值传递的一个可能的原因,它的语言设计者人们希望简化的语言,让一切都在面向对象的方式进行。
他们宁愿有你设计的整数交换技术使用对象不是将它们用于通过参考通过提供第一类支持,同为委托(斯林感觉恶心与指针运行,他宁愿塞进该功能的对象),并枚举。
它们过度简化(一切都是对象)的语言不具有对于大多数语言结构,例如第一类载体的损害通过引用,委托,枚举传递,属性想到。
你确定它打印空?我认为这将是只是空白,当您初始化您提供空字符串foo的变量。
的foo在thisDoesntWork方法中的分配不改变类中定义,以便在的System.out.println(富)foo的foo的变量的基准仍将指向旧的空字符串对象。
戴夫,你要原谅我(好吧,我想你不“要”,但我宁愿你这样做),但这个解释是不是太有说服力。安全收益是相当小的,因为任何人谁需要改变字符串的值会想办法用一些丑陋的变通办法做到这一点。和速度?你自己(完全正确)断言,整个业务与+是极其昂贵的。
你们的其余部分,请大家明白,我得到它是如何工作的,我问为什么它这样...请停止解释方法之间的区别。
(老实说,我不是在寻找任何形式的战斗在这里,顺便说一句,我只是不明白如何,这是一个理性的决定)。
@Axelle
配合你真的值和参考知道传球的区别?
在的java甚至参考值传递。当你传递一个参考到对象您得到在第二可变基准指针的一个拷贝。 Tahts为什么第二可变可以在不影响第一被改变。
这是因为,它创建的方法内的局部变量。这将是一个简单的方法(我敢肯定会工作)将是:
String foo = new String();
thisDoesntWork(foo);
System.out.println(foo); //this prints nothing
public static void thisDoesntWork(String foo) {
this.foo = foo; //this makes the local variable go to the main variable
foo = "howdy";
}
如果你认为一个对象作为只在接着的目的是通过在Java中引用传递因为一种方法可修改参数的字段和一个呼叫者可以观察修改该对象的字段。不过,如果你也认为一个对象,因为它的身份,然后对象是按值传递,因为一个方法不能改变一个参数的身份的方式,调用者可以观察到。所以,我要说Java是传址值。
这是因为里面的“thisDoesntWork”时,实际上是破坏foo的本地值。如果要通过参考以这种方式来传递,总是可以封装另一个物体内部的字符串,表示在阵列中。
class Test {
public static void main(String[] args) {
String [] fooArray = new String[1];
fooArray[0] = new String("foo");
System.out.println("main: " + fooArray[0]);
thisWorks(fooArray);
System.out.println("main: " + fooArray[0]);
}
public static void thisWorks(String [] foo){
System.out.println("thisWorks: " + foo[0]);
foo[0] = "howdy";
System.out.println("thisWorks: " + foo[0]);
}
}
结果在下面的输出:
main: foo
thisWorks: foo
thisWorks: howdy
main: howdy
参考类型参数被作为到对象本身的引用传递(到其他未引用的变量引用的对象)。您可以拨打已通过对象的方法。然而,您的代码示例中:
public static void thisDoesntWork(String foo){
foo = "howdy";
}
你只存储在变量,它是本地的方法到字符串"howdy"
的参考。该局部变量(foo
)被初始化的时候被调用该方法的调用者的foo
的价值,但没有提及调用者的变量本身。初始化后:
caller data method
------ ------ ------
(foo) --> "" <-- (foo)
后在方法中的分配:
caller data method
------ ------ ------
(foo) --> ""
"hello" <-- (foo)
您有其他问题还有:String
实例是不可变(设计,出于安全考虑),所以你不能修改其值
如果你真的想你的方法来为您提供字符串的初始值(或任何的时间在它的生命,对于这个问题),然后让你的方法的返回的其中分配给调用者的变量在调用的点String
值。这样的事情,例如:
String foo = thisWorks();
System.out.println(foo);//this prints the value assigned to foo in initialization
public static String thisWorks(){
return "howdy";
}
转到做太阳网站真正的大教程。
您似乎并没有理解上的差异作用域变量即可。 “富”是当地对你的方法。该方法之外没有任何东西可以改变什么“foo”的分太少。在“富”被称为你的方法是完全不同的领域 - 它是一个静态字段上的封闭类。
,你不想要的一切是在你的系统一切可见的范围界定就显得尤为重要。