黑马程序员技术交流社区

标题: 关于原数据会不会改变 [打印本页]

作者: 燿陚√揚葳    时间: 2014-7-9 20:45
标题: 关于原数据会不会改变
本帖最后由 燿陚√揚葳 于 2014-7-10 11:37 编辑

今天做练习时,碰到的问题,如:
public static void main(){

int x=10;
show(x);
System.out.println(x);
}

public static void show(int x){
x=0;
}
相信都知道这个输出结果是什么
由此而引出的很多文题也就出来了,一个方法调用另一个方法时,用到它的数据,是不是原数据一定不会受到影响呢
比如数组为什么就会改变了原来的数据呢?
如:
public static void main(){

int[] arr={4,5,6,7,9,8,53,1,2,0};
  t.changeShuzu(arr);
  for(Object str:arr){
   System.out.print(str+"*");
  }
}


public static void changeShuzu(int[] args){
  for(int x=0;x<args.length;x++){
   if(args[x]>4)
    args[x]=0;
  }
}
他的数据为什么会改变呢?
这里总结了一些关于这方面的知识,希望对大家有所帮助

首先
与其他语言不同,Java不允许程序员选择按值传递还是按引用传递各个参数,
基本类型(byte--short--int--long--float--double--boolean--char)的变量总是按值传递。
就对象而言,不是将对象本身传递给方法,而是将对象的的引用或者说对象的首地址传递给方法,
引用本身是按值传递的-----------也就是说,讲引用的副本传递给方法(副本就是说明对象此时有两个引用了),
通过对象的引用,方法可以直接操作该对象(当操作该对象时才能改变该对象,而操作引用时源对象是没有改变的)。

如代码:


  1. public class Test {

  2. public void changeInt(int i) {
  3. i = 5;
  4. }

  5. public void changeLong(long i) {
  6. i = 555;
  7. }

  8. public void changeString(String str) {
  9. str = "5555";
  10. }

  11. public void changeMember(Member member) {
  12. member = new Member("Cindy", 35);
  13. }

  14. public void changeMemberField(Member member) {
  15. member.setAge(20);
  16. member.setName("Andy");
  17. }

  18. public void changeShuzu(String[] args){
  19. for(int x=0;x<args.length;x++){
  20. if(args[x]=="aaa")
  21. args[x]="111";
  22. }
  23. }

  24. public void change(StringBuffer x, StringBuffer y) {

  25. x.append(y);
  26. y = x;
  27. }

  28. public static void main(String[] args) {
  29. Test t = new Test();

  30. int intValue = 10;
  31. t.changeInt(intValue);
  32. System.out.println(intValue);


  33. long longValue = 10;
  34. t.changeLong(longValue);
  35. System.out.println(longValue);


  36. String stringValue = "1234";
  37. t.changeString(stringValue);
  38. System.out.println(stringValue);


  39. String[] strs={"aaa","bbb","ccc","ddd","eee","fff"};
  40. t.changeShuzu(strs);
  41. for(String str:strs){
  42. System.out.print(str+"*");
  43. }
  44. System.out.println();


  45. Member member2 = new Member("Douglas", 45);
  46. t.changeMember(member2);
  47. System.out.println(member2);

  48. Member member = new Member("Bill", 25);
  49. t.changeMemberField(member);
  50. System.out.println(member);


  51. StringBuffer buffA = new StringBuffer("a");
  52. StringBuffer buffB = new StringBuffer("b");
  53. t.change(buffA, buffB);
  54. System.out.println(buffA + "," + buffB);
  55. }





  56. }

  57. class Member {
  58. private String name;
  59. private int age;

  60. public Member(String name, int age) {
  61. this.age = age;
  62. this.name = name;
  63. }

  64. public String toString() {
  65. return "Member name=" + name + " age=" + age;
  66. }


  67. public void setAge(int age) {
  68. this.age = age;
  69. }


  70. public void setName(String name) {
  71. this.name = name;
  72. }
  73. }
复制代码
输出的结果为
10
10
1234
111*bbb*ccc*ddd*eee*fff*
Member name=Douglas age=45
Member name=Andy age=20
ab,b

结果的分析:
第一个输出10是因为int是基本类型,传递的参数是intValue的拷贝,对拷贝的修改对原值intValue没有影响.
第一个输出10和上面是一个道理.
第三个输出1234.由于String是类类型, str是stringValue的地址拷贝,参数str指向的地址和stringValue的一致,
但在函数changeString 中,由于String的特殊性, str=“5555”和str=new String(“5555”)是等价的,
str指向了新的”5555”所在的地址,此句后str就与原来的stringValue彻底脱离了联系.
第四个输出的是111*bbb*ccc*ddd*eee*fff*。如果将单个基本类型数组的元素传递给方法,并在方法中对其进行修改,
则在被调用方法结束执行时,该元素中存储的并不是修改后的值,因为这种元素是按值传递,如果传递的是数组的引用,
则对数组元素的后续修改可以在原始数组中反映出来(因为数组本身就是个对象,int[] a = new int[2];,
这里面的int是数组元素的类型,而数组元素的修改是操作对象)。
于单个非基本类型数组的元素在方法中修改,则在被调用方法结束执行时,该元素中存储的是修改后的值,
因为这种元素是按引用传递的,对象的改动将在源数组的数组元素中反映出来。

第五个输出Member?name=Douglas?age=45的道理和上面相同.
第六个输出Member?name=Andy?age=20是因为changeMemberField函数中修改了参数member
的值,也就是修改member指向实例的值,而这个实例正是member指向的值,因此member就变成了name=Andy 且age=20.
就像是数组一样都是对对象的操作所以会改变原数据
第七个输出的是ab,b。
在方法change()里 的x.append(y),其中引用x调用api方法append()修改了new StringBuffer("a");的内容。
y=x;是一个修改内容的对象把首地址赋值给引用变量y了,此时操作的是引用,
而先前y是new StringBuffer("b");的引用变量,所以输出结果是:ab,b

总结:
Java参数,不管是原始类型还是引用类型,传递的都是副本(有另外一种说法是传值,但是说传副本更好理解吧,传值通常是相对传址而言)。
如果参数类型是原始类型,那么传过来的就是这个参数的一个副本,也就是这个原始参数的值,
这个跟之前所谈的传值是一样的。如果在函数中改变了副本的 值不会改变原始的值.
如果参数类型是引用类型,那么传过来的就是这个引用参数的副本,这个副本存放的是参数的地址。
如果在函数中没有改变这个副本的地址,而是改变了地址中的 值,那么在函数内的改变会影响到传入的参数。
如果在函数中改变了副本的地址,如new一个,那么副本就指向了一个新的地址,
此时传入的参数还是指向原来的 地址,所以不会改变参数的值。




作者: zhyhbk    时间: 2014-7-9 20:51
支持一下
作者: 燿陚√揚葳    时间: 2014-7-9 20:51
zhyhbk 发表于 2014-7-9 20:51
支持一下

谢谢支持:)
作者: cat73    时间: 2014-7-9 21:04
事实上严格来讲
函数内绝对不可能直接改变函数外传进来的参数

所谓的引用时修改属性,其实传进来的是一个结构体的地址
而你修改的是那个结构体里面的内容
传进来的地址你还是无法修改的
作者: fantacyleo    时间: 2014-7-9 21:18
好长。。。参数传递是老生常谈的话题。值传递、引用传递、传值、传址,其实是我们自己把事情搞复杂了。C和Java都只有一种参数传递方式:按值传递。Oracle官方的Java tutorial写得很清楚:
Primitive arguments, such as an int or a double, are passed into methods by value.

Reference data type parameters, such as objects, are also passed into methods by value

之所以冒出来按引用传递,很大程度上是我们没搞清楚“变量的值”的含义,或者把地址当作一种神秘的东西,好像跟1、2、3、'a'、'b'、'c'有本质的区别。

作者: 燿陚√揚葳    时间: 2014-7-9 21:32
cat73 发表于 2014-7-9 21:04
事实上严格来讲
函数内绝对不可能直接改变函数外传进来的参数

嗯谢谢你的提议,我看过一些这方面的东西,网上的说法也各有千秋,我只是把算是比较大众化的一些结论收集总结一下,对于这些我有时感觉比较模糊,也没有一个比较权威性的说法,能不能把你的一些想法再说的具体一些,或者说更通俗更详细一些,最好有一些代码列子,我也好好总结一下,把这些知识完善一下,不然说的不准确或者有的地方漏掉了,别人也会看不懂的,谢谢你了!
作者: doubandddk    时间: 2014-7-9 21:36
标题: 学习下
本帖最后由 doubandddk 于 2014-7-9 21:43 编辑

学习下 ,这个我也不大明白了
作者: 燿陚√揚葳    时间: 2014-7-9 21:37
fantacyleo 发表于 2014-7-9 21:18
好长。。。参数传递是老生常谈的话题。值传递、引用传递、传值、传址,其实是我们自己把事情搞复杂了。C和J ...

也就说权威性的说法,Java只有值传递没有什么引用传递了,对吗?
谢谢你的告知
作者: 燿陚√揚葳    时间: 2014-7-9 21:43
doubandddk 发表于 2014-7-9 21:36
所谓的传值调用,调用方法时都是传一个具体值进去,会在内存中生成这个值的一个副本;在被调用的方法内部使 ...

恩,我见网上也是这么说的,我也是今天碰到一些问题,总是对这些概念比较模糊,所以找一些资料看看,发表出来,把它完善一下,谢谢你的提议了




欢迎光临 黑马程序员技术交流社区 (http://bbs.itheima.com/) 黑马程序员IT技术论坛 X3.2