這是一個很基礎的問題,如果你已經了解透徹了,其實可以不需要往下看(如果了解沒錯的話),因為相信你已經知道了答案,本篇主要是解釋給和我一樣一直以來有這樣誤解的人,事實上這是一個簡單的問題,之是以會陷入這個誤區,主要還是因為習慣了進階語言後,特别是屏蔽了指針感覺後,多年來“口口相傳”導緻的誤解。
起因是:
關于 dart 在函數裡究竟是引用還是傳值,到 java 在方法裡是引用還是傳值?
其實結論也很簡單,不管是 dart 和 java ,在正統意義上了解,都是值傳遞。 等下,是不是這時候有些人就開始質疑了:“就這”?
不急,有興趣的可以往下看,先說正統意義上的了解,如下示例1代碼所示,這就是正統意義上傳遞還是引用的最直覺示例:
///示例1
public static class People {
public String name;
People(String name) {
this.name = name;
}
}
public static void main(String[] args) {
People a = new People("111");
changePeople(a);
System.out.println("print " + a.name);
}
public static void changePeople(People p) {
p = new People("222");
}
如上代碼,如果是真正意義上的引用傳遞,那麼列印出來的應該是
"print 222"
,但是事實上運作後列印出來的是
"print 111"
。
如果你覺得這樣不對,那就是和我以前一樣了解錯誤的話,那肯定會舉這樣的例子,如下示例2所示:
///示例2
public static class People {
public String name;
People(String name) {
this.name = name;
}
}
public static void main(String[] args) {
People a = new People("111");
System.out.println("print a hash " + a.hashCode());
changePeople(a);
System.out.println("print " + a.name);
}
public static void changePeople(People p) {
System.out.println("print p hash " + p.hashCode());
p.name = "222";
}
運作之後的結果是:
I/System.out: print a hash 240863055
I/System.out: print p hash 240863055
I/System.out: print 222
分明
a
和
p
不就是一個位址嗎?列印之後
a
的
name
不也變成了
222
了嗎? 從這個角度了解看起來好像真的就是引用傳遞!但是可惜這并不是,這是一種誤解。
其實這裡的問題主要出在讨論的角度出現了問題:
- 示例 1 正統上大家說的引用傳遞是對于變量對象的角度;
- 示例 2 讨論的引用還是傳遞是以值的角度;
知乎的這個例子舉的就特别有意思,以它的例子為模闆:
- 你有一把鑰匙,當你的朋友想要去你家的時候,如果你直接把你的鑰匙給他了,這就是引用傳遞。這種情況下,如果他對這把鑰匙做了什麼事情,比如他在鑰匙上刻下了自己名字,那麼這把鑰匙還給你的時候,你自己的鑰匙上也會多出他刻的名字。、
- 你有一把鑰匙,當你的朋友想要去你家的時候,如果你複制了你的鑰匙給他,這就是值傳遞。這種情況下,如果他對他鑰匙做了什麼事情,都和你的鑰匙無關。
- 最後按照示例2的角度代入這個故事,你的朋友拿着你給他的鑰匙,進到你的家裡,把你家的電視砸了,你再用你的鑰匙開門進去,看到的也是被砸了的家,這就是示例2中的
指派的類比。p.name
是以示例2其實就是如上圖的一個狀态,其實
a
傳遞進去
changePeople
之後,在
changePeople
裡的
p
已經是另外一個位址,而不是傳遞的
a
的位址 ,是以并不是傳統意義上的引用傳遞,而我們列印出來的一緻的
hashCode
,其實就是值
People
的位址和引用。
這個結論在 java 和 dart 裡都是一緻的,而我也是被 js 的同學所打臉,是以在函數上 java、dart、js 這些進階語言的設計都是如此。
我思考了下,從值的角度導緻誤解出現的原因,其實應該歸結于進階語言裡屏蔽了指針等的底層概念:
首先在 java、 dart 函數裡讨論對象的傳遞引用意義不大,因為不能被操作的引用對象沒意義,如果引用對象不被指派給變量,它就會被GC,是以最終都關注到“值”本身。
是以作為操作不了對象引用的語言,讨論引用傳遞确實沒有意義,進而導緻大家把值和對象關系搞混了。
Dart/Flutter中對象傳值(by value)還是引用(by ref)
之前看到有個文章url說是傳值,我讀了一些資料,覺得是錯誤的!正确的答案是引用。
參考這篇文章 https://stackoverflow.com/questions/54545977/dart-variable-store-reference-to-the-value
import 'dart:core';
class A{
int index = 0;
}
void setv_i(int a){
a += 1;
}
void setv_A(A a){
a.index += 1;
}
void main() {
// https://stackoverflow.com/questions/54545977/dart-variable-store-reference-to-the-value
// operator = 是變量名對一個記憶體對象的ref。
// i 與 aa 表現不同是因為 num和String屬于immutable類型,每次都是建立一個對象。
A aa = A();
print("old: A.index=${aa.index}");
setv_A(aa);
print("new: A.index=${aa.index}");
// 一個具有代表性的例子
{
var a = ['Apple', 'Orange'];
var b = a; // a,b ref to a some obj
a = ['Banana']; // Assignment, no impact to b, then a ref new obj
print(a); // [Banana]
print(b); // [Apple, Orange]
}
{
var a = ['Apple', 'Orange'];
var b = a;
a.clear(); // Mutation, the _list instance_ is changed, a's ref is same
a.add('Banana'); // Another mutation
print(a); // [Banana]
print(b); // [Banana]
}
}
輸出結果如下:
old: A.index=0
new: A.index=1
[Banana]
[Apple, Orange]
[Banana]
[Banana]
核心要點:
-
這種指派文法的含義是a是指向一個記憶體對象10的引用(ref).var a = 10;
-
則a,b都指向同一個記憶體對象,即引用同一個記憶體對象。但是a,b之間沒有任何聯系。var a = 10; var b=a;
- 在2的情況下,修改a指向對象的值,b也會跟着改。(沖突點也在這裡!)
- 但是,Dart中不是所有的對象都可以修改的(mutable or immutable),其中number,String,bool等都是不可變類型,每次修改都是産生一個新的對象。而其他大部分對象都是mutable, 是以第3條的情況在面對immutable對象會表現不一緻。
Dart是值傳遞還是引用傳遞
1)在dart中對于基礎資料類型String、int、bool、double是值傳遞也就是說給傳入函數内的形參指派不會改變外部變量的入參的值
2)而對象List、Set、Map、class是引用傳遞
我們每次調用函數,傳遞過去的對象都是對象的記憶體位址,而不是這個對象的複制。
參數是把記憶體位址傳過去了,如果對這個記憶體位址上的對象修改,那麼其他位置的引用該記憶體位址的變量值也會修改。
例一:
以上例子中,傳入全局變量element.photoInfoModelList,在yzCallbackWithPhotoList回調函數中給傳入的photoInfoModelList指派,不會改變全局變量element.photoInfoModelList的值;因為submitPhoto中使用的局部變量photoInfoModelList的記憶體位址已經發生改變,此時指向的是list的記憶體位址
例二:
以上_goodItemCard接收的是一個model,在使用時傳入了全局變量ctr.list[i],在_goodItemCard内對model類屬性value和valueName進行指派操作同步改變了ctr.list[i]中的value和valueName的值,因為此時傳入的model的記憶體位址沒有發生改變。
看了上面的例子可能會反駁,第一個例子為什麼就不能改變外部變量,這不是不對嗎?
實際上,還是這句話“dart是引用傳遞”
後續修改都是0x0002222記憶體的修改
總結