一文淺析Java中的值傳遞
LeetCode 113
問題:給你二叉樹的根節(jié)點root和一個整數(shù)目標和targetSum,找出所有 從根節(jié)點到葉子節(jié)點 路徑總和等于給定目標和的路徑。
示例
輸入:root = [5,4,8,11,null,13,4,7,2,null,null,5,1], targetSum = 22
輸出:[[5,4,11,2],[5,8,4,5]]
我的代碼如下
class Solution { public void traversal(TreeNode root, int count, List<List<Integer>> res, List<Integer> path) { path.add(root.val); if (root.left == null && root.right == null) { if (count - root.val == 0) { res.add(path); } return; } ? if (root.left != null) { traversal(root.left, count - root.val, res, path); path.remove(path.size() - 1); } if (root.right != null) { traversal(root.right, count - root.val, res, path); path.remove(path.size() - 1); } } ? public List<List<Integer>> pathSum(TreeNode root, int targetSum) { List<List<Integer>> res = new ArrayList<>(); List<Integer> path = new ArrayList<>(); if (root == null) return res; traversal(root, targetSum, res, path); ? return res; } }
該題的思路是采用遞歸,traversal函數(shù)內root是當前樹的根節(jié)點,count是目標值,res是存儲結果,path是路徑。該代碼對于示例的輸入輸出為
輸入:root = [5,4,8,11,null,13,4,7,2,null,null,5,1], targetSum = 22
輸出:[[5],[5]]
經過排查最終問題在于代碼中的add方法
原代碼部分內容為
if (root.left == null && root.right == null) { if (count - root.val == 0) { res.add(path); } return; }
該部分內容需要改為
if (root.left == null && root.right == null) { if (count - root.val == 0) { res.add(new ArrayList(path)); } return; }
此時所有代碼對于示例的輸入輸出為
輸入:root = [5,4,8,11,null,13,4,7,2,null,null,5,1], targetSum = 22
輸出:[[5,4,11,2],[5,8,4,5]]
在java中,存在8大基本數(shù)據(jù)類型,且均有對應的包裝類
數(shù)據(jù)類型 | 占用位數(shù) | 默認值 | 包裝類 |
---|---|---|---|
byte(字節(jié)型) | 8 | 0 | Byte |
short(短整型) | 16 | 0 | Short |
int(整型) | 32 | 0 | Integer |
long(長整型) | 64 | 0.0l | Long |
float(浮點型) | 32 | 0.0f | Float |
double(雙精度浮點型) | 64 | 0.0d | Double |
char(字符型) | 16 | "/u0000" | Character |
boolean(布爾型) | 1 | false | Boolean |
在java中,函數(shù)傳遞只有值傳遞,是指在調用函數(shù)時,將實際參數(shù)復制一份傳遞給函數(shù),這樣在函數(shù)中修改參數(shù)(形參)時,不會影響到實際參數(shù)。
基本數(shù)據(jù)類型的值傳遞
測試類
public class TestClass { public static void test(int value) { value = 2; System.out.println("形參value的值:" + value); } ? public static void main(String[] args) { int value = 1; System.out.println("調用函數(shù)前value的值:" + value); test(value); System.out.println("調用函數(shù)后value的值:" + value); } }
結果為
調用函數(shù)前value的值:1
形參value的值:2
調用函數(shù)后value的值:1
結論:可以看到,int類型的value初始為1,調用函數(shù)后,value仍然為1,基本數(shù)據(jù)類型在函數(shù)中修改參數(shù)(形參)時不會影響到實參的值。
引用數(shù)據(jù)類型的值傳遞
類TreeNode
public class TreeNode { int val; TreeNode left; TreeNode right; ? TreeNode() { } ? TreeNode(int val) { this.val = val; } ? TreeNode(int val, TreeNode left, TreeNode right) { this.val = val; this.left = left; this.right = right; } }
測試類1
public class TestClass { public static void test(TreeNode node) { node.val = 2; System.out.println("形參node的val值:" + node.val); } ? public static void main(String[] args) { TreeNode node = new TreeNode(1); System.out.println("調用函數(shù)前node的val值:" + node.val); test(node); System.out.println("調用函數(shù)后node的val值:" + node.val); } }
結果為
調用函數(shù)前node的val值:1
形參node的val值:2
調用函數(shù)后node的val值:2
結論:可以看到,TreeNode類型的node對象的val值初始為1,調用函數(shù)后,node對象的val值被修改為2,引用數(shù)據(jù)類型在函數(shù)中修改參數(shù)(形參)時影響到了實參的值。
現(xiàn)在看另一個示例
測試類2
public class TestClass { public static void test(TreeNode node) { node = new TreeNode(2); System.out.println("形參node的val值:" + node.val); } ? public static void main(String[] args) { TreeNode node = new TreeNode(1); System.out.println("調用函數(shù)前node的val值:" + node.val); test(node); System.out.println("調用函數(shù)后node的val值:" + node.val); } }
結果為
調用函數(shù)前node的val值:1
形參node的val值:2
調用函數(shù)后node的val值:1
結論:可以看到,TreeNode類型的node對象的val值初始為1,調用函數(shù)后,node對象的val值仍然為1,引用數(shù)據(jù)類型在函數(shù)中修改參數(shù)(形參)時未影響到實參的值。
那么,為什么會出現(xiàn)這種問題呢?
首先,在JAVA中,函數(shù)傳遞都是采用值傳遞,實際參數(shù)都會被復制一份給到函數(shù)的形式參數(shù),所以形式參數(shù)的變化不會影響到實際參數(shù),基本數(shù)據(jù)類型的值傳遞示例可以發(fā)現(xiàn)這個性質。但引用數(shù)據(jù)類型的值傳遞為什么會出現(xiàn)修改形式參數(shù)的值有時會影響到實際參數(shù),而有時又不會影響到實際參數(shù)呢?其實引用數(shù)據(jù)類型傳遞的內容也會被復制一份給到函數(shù)的形式參數(shù),這個內容類似C++中的地址,示例中的node對象存儲于堆中,雖然形參與實參是兩份內容,但內容值相同,都指向堆中相同的對象,故測試類1在函數(shù)內修改對象值時,函數(shù)外查看時會發(fā)現(xiàn)對象值已被修改。測試類2在函數(shù)內重新構造了一個對象node,在堆中申請了一個新對象(新對象與原對象val值不相同),讓形參指向這個對象,所以不會影響到原對象node的值。測試類1與測試類2的區(qū)別在于引用數(shù)據(jù)類型的指向對象發(fā)生了變化。
以下代碼可驗證上述分析
測試類1
public class TestClass { public static void test(TreeNode node) { System.out.println("test:node" + node); node.val = 2; System.out.println("test:node" + node); System.out.println("形參node的val值:" + node.val); } ? public static void main(String[] args) { TreeNode node = new TreeNode(1); System.out.println("調用函數(shù)前node的val值:" + node.val); System.out.println("main node:" + node); test(node); System.out.println("調用函數(shù)后node的val值:" + node.val); System.out.println("main node:" + node); } }
結果為
調用函數(shù)前node的val值:1
main node:TreeNode@1540e19d
test:nodeTreeNode@1540e19d
test:nodeTreeNode@1540e19d
形參node的val值:2
調用函數(shù)后node的val值:2
main node:TreeNode@1540e19d
測試類2
public class TestClass { public static void test(TreeNode node) { System.out.println("test:node" + node); node = new TreeNode(2); System.out.println("test:node" + node); System.out.println("形參node的val值:" + node.val); } ? public static void main(String[] args) { TreeNode node = new TreeNode(1); System.out.println("調用函數(shù)前node的val值:" + node.val); System.out.println("main node:" + node); test(node); System.out.println("調用函數(shù)后node的val值:" + node.val); System.out.println("main node:" + node); } }
結果為
調用函數(shù)前node的val值:1
main node:TreeNode@1540e19d
test:nodeTreeNode@1540e19d
test:nodeTreeNode@677327b6
形參node的val值:2
調用函數(shù)后node的val值:1
main node:TreeNode@1540e19d
對于測試類1,形參和實參都是指向相同的對象,所以利用形參修改對象的值,實參指向的對象的值發(fā)生改變。對于測試類2,形參在函數(shù)開始和實參指向相同的對象,讓其指向新的對象后,實參指向的對象的值不會發(fā)生改變。簡要說,測試類1形參復制了實參的地址,修改了地址對應的對象值,但并未修改地址值,測試類2形參復制了實參的地址,并修改了地址值,但并未修改原地址值對應的對象值。
有了目前的結論,可以理解為什么res.add()函數(shù)內path修改為new ArrayList(path)就可代碼運行成功。因為我的path類型為List<Integer>,為引用數(shù)據(jù)類型,且path的值一直在發(fā)生變化。隨著遞歸代碼的運行,path的值發(fā)生變化,res內最初的List<Integer>值會發(fā)生變化(就是path的值)。但將path修改為new ArrayList(path)后,是在堆中新構造了對象,并指向該對象,原對象的變化不會影響到該對象的值,那么res內List<Integer>值就不會發(fā)生變化。
listList.add()方法直接傳入list1
import java.util.ArrayList; import java.util.List; ? public class TestClass { public static void main(String[] args) { List<List<Integer>> listList = new ArrayList<>(); List<Integer> list1 = new ArrayList<>(); list1.add(1); listList.add(list1); //直接add list1 List<Integer> list2 = new ArrayList<>(); list2.add(2); listList.add(list2); System.out.println("list1改變前"); for (List<Integer> l : listList) { for (Integer i : l) { System.out.println(i); } System.out.println("---"); } list1.set(0, 2); //將list1的0號元素改為2 System.out.println("list1改變后"); for (List<Integer> l : listList) { for (Integer i : l) { System.out.println(i); } System.out.println("---"); } } }
結果為
list1改變前
1
---
2
---
list1改變后
2
---
2
---
listList.add()方法重新構造新對象(內容與list1相同)
import java.util.ArrayList; import java.util.List; ? public class TestClass { public static void main(String[] args) { List<List<Integer>> listList = new ArrayList<>(); List<Integer> list1 = new ArrayList<>(); list1.add(1); listList.add(new ArrayList<>(list1)); //構造新對象 再調用add List<Integer> list2 = new ArrayList<>(); list2.add(2); listList.add(list2); System.out.println("list1改變前"); for (List<Integer> l : listList) { for (Integer i : l) { System.out.println(i); } System.out.println("---"); } list1.set(0, 2); //將list1的0號元素改為2 System.out.println("list1改變后"); for (List<Integer> l : listList) { for (Integer i : l) { System.out.println(i); } System.out.println("---"); } } }
結果為
list1改變前
1
---
2
---
list1改變后
1
---
2
---
到此這篇關于一文淺析Java中的值傳遞的文章就介紹到這了,更多相關Java值傳遞內容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關文章希望大家以后多多支持腳本之家!
相關文章
圖解Springboot集成七牛云并實現(xiàn)圖片上傳功能過程
在實際開發(fā)中 ,基本都會有應用到文件上傳的場景,但隨著或多或少的需求問題,之前有在springboot上用過七牛云實現(xiàn)圖片上傳,今天因為某些原因又重新使用了下七牛云因此想總結下七牛云2021-11-11Springboot?整合?RocketMQ?收發(fā)消息的配置過程
這篇文章主要介紹了Springboot?整合?RocketMQ?收發(fā)消息,本文給大家介紹的非常詳細,對大家的學習或工作具有一定的參考借鑒價值,需要的朋友可以參考下2021-12-12Spring Cloud 的 Hystrix.功能及實踐詳解
這篇文章主要介紹了Spring Cloud 的 Hystrix.功能及實踐詳解,Hystrix 具備服務降級、服務熔斷、線程和信號隔離、請求緩存、請求合并以及服務監(jiān)控等強大功能,需要的朋友可以參考下2019-07-07