寫在前邊
- 上次聊到Java8新特性 lambda時,有小伙伴在評論區提及到了lambda對于區域變數的參考,補充著博客的時候,知識點一發散就有了這篇對于值傳遞還是參考傳遞的思考,關于這個問題為何會有如此多的誤區,這篇就來破解ta!
果然知識網的發散是無止境的!
知識儲備--堆和堆疊
- 堆是指動態分配記憶體的一塊區域,一般由程式員手動分配,比如 Java 中的 new、c里邊的malloc,
- 堆疊是編譯器幫我們分配好的區域,一般用于存放函式的引數值,區域變數等
有關堆疊的相關知識在 迷途指標 中有所提及,
資料型別
Java中除了基本資料型別,其他的均是參考型別,包括類、陣列等等,
基本資料型別和參考型別的區別
先看一下這兩個變數的區別
void test1(){
int cnt = 0;
String str = new String("melo");
}
- cnt是基本型別,值就直接保存在變數中(存放在堆疊上)
- 而str是參考型別,變數中保存的只是實際物件的地址,一般稱這種變數為"參考",參考指向實際物件,實際物件中保存著內容,
比如我們創建了一個 Student student = new Student("Melo");
- 在堆中開辟一塊記憶體(真正的物件存放在堆上),其中保存了name等資料 , 而student只是保存了該物件的地址(存放在堆疊上)
當我們修改變數時
void test1(){
int cnt = 0;
cnt=1;
String str = new String("melo");
str="Melo";
}
對于基本型別 cnt,賦值運算子會直接改變變數的值,原來的值直接被覆寫掉了,
ta無依無靠,不像下邊一樣有房子可以住,
對于參考型別 str,賦值運算子只會改變參考中所保存的地址,雖然原來的地址被覆寫掉了,str指向了一個新的物件,但是原來的那個老物件沒有發生變化,他還是老老實實待在原來的地方!!!
有學過c語言的同學應該很清楚,這里借助c語言中的“指標”打個比喻,
- 參考型別str就相當于一個指標(旗子),插在了一個房子門口,現在給這個旗子挪個位置,只是讓這個旗子放置在了另一個新的房子,原本的老房子還在那里,不會說因為你改變了旗子的位置,房子就塌了,
當然,原來那個房子沒有旗子插著了,沒有人住了,也不能總是放任ta在那占著空間,過段時間也許就會有人來把他給拆了回收了(JVM),
這種沒有地方參考到的物件就稱為垃圾物件,
值傳遞
我們上次聊到lambda的時候,提及到了值傳遞,那里的拷貝副本,就是我們這里要說的值傳遞
- 如果我們這里的方法塊訪問了外部的變數,而這個變數只是一個普通資料型別的話,相當于只是訪問到了一份副本,當外部對這個變數進行修改時,lambda內部(只有副本)是無法感知到這個變數的修改的,
我們只是將實參傳遞給了方法的形參,將cnt值復制一份,賦值給形參val所以,函式內對形參的操作完全不會影響到實參真正存活的區域!而伴隨著函式呼叫的結束,形參區域和其內的區域變數也會被釋放,(方法堆疊的回收)
//基本型別的值傳遞
void unChange(int val) {
val = 100;
}
unChange(cnt); // cnt 并沒有被改變
參考傳遞
實參傳遞給形參時,形參其實用的就是實參本身(而不再單純只是拷貝一份副本出來了),當該形參變數被修改時,實參變數也會同步修改,
Java中到底是參考傳遞還是值傳遞呢
內卷實體
//內卷
void involution(Student temp){
temp.setScore(100);
}
public static void main(String[] args) {
Student student = new Student();
student.setName("Melo");
student.setScore(0);
System.out.println("躺平時的成績->"+student.getScore());
new TestQuote().involution(student);
System.out.println("卷了幾天后的成績->"+student.getScore());
}
- 這里看起來,好像符合我們參考傳遞的定義誒?
- 對形參temp的修改,會反饋到外部實參student那里去?看起來操作的是同一個變數的樣子?
反內卷實體
看下邊這段"反內卷"的代碼實體
//反內卷
void againInvolution(Student temp){
temp = new Student();
temp.setScore(100);
}
public static void main(String[] args) {
Student student = new Student();
student.setName("Melo");
student.setScore(0);
System.out.println("企圖內卷前的成績->"+student.getScore());
new TestQuote().againInvolution(student);
System.out.println("遭受反內卷后的成績->"+student.getScore());
}
- 細心的同學可能發現了,我們這里多了一步操作 --> temp = new Student();
?
先給出答案吧,Java里邊其實只有值傳遞!!!
-
為什么這么說?
其實我們這里的形參temp,只是拷貝了一份student的地址,可以理解為temp拷貝了這條指標,他也指向了student所指向的物件, -
也就是說,temp只是跟student同樣指向了一個同一個物件而已,在第一個例子中,我們沒有去重新修改temp的指向,所以會造成一種假象:我們對temp的修改似乎等價于對student的修改? 其實只是剛好兩個指向了同一個物件而已!!
- 而如果我們對temp重新賦值了呢, temp = new Student();
- 對temp重新賦值后,此時temp就指向了另一個區域了,后續再對temp修改,根本不會影響原來的student指向的區域
所以才會"反內卷"失敗,跳出函式的時候,student所指向的物件成績根本沒有增長!!!
?
為什么會有誤區呢?
- 其實還是因為Java中資料型別的問題,基本資料型別看起來就像是值傳遞,而參考傳遞因為存放了地址,讓我們能夠訪問到實參所指向的物件,容易讓我們誤以為我們的形參其實就等價于實參.
其他語言的參考
JS只有值傳遞,類似Java
指標傳遞(C語言)
注意指標傳遞跟參考傳遞是不一樣的
- 拿最老套的C語言手寫swap來講
#include <stdio.h>
void swap(int *a, int *b) {
int temp;
temp = *a;
*a = *b;
*b = temp;
}
int main() {
int a = 5;
int b = 8;
//需要傳遞地址
swap(&a, &b);
printf("a = %d\n", a);
printf("b = %d", b);
}
參考傳遞(C++)
#include <iostream>
using namespace std;
int main()
{
//&識別符號
void swap(int& x,int& y);
int a = 5;
int b = 8;
swap(a,b);
return 0;
}
void swap(int& a,int& b){
int temp;
temp = a;
a = b;
b = temp;
}
總結
如果該語言沒有&,@這種取地址的運算子,一般來說就只有值傳遞的,如js和java,
經評論區小伙伴補充,不用&,@這種取地址的運算子也可以參考傳遞,參考C#的ref、out和in關鍵字,
- 而c,Pascal,go這些是可以傳參考和傳值的,
最后
- 其實關于Java到底是參考傳遞還是值傳遞這個問題,我們只需要理解好本質就好了,通過上邊的那兩幅圖,理解好本質才是關鍵,萬變不離其宗,
轉載請註明出處,本文鏈接:https://www.uj5u.com/houduan/349475.html
標籤:Java
