本文记录了java中的值传递和引用传递,String的两种赋值方式的一些学习要点。
形参传递
- java中的形参传递都是单向传递,传递的是原变量的副本
- 在方法中改变的是副本的值,而不是原变量的
值传递和引用传递
- java语言中只有值传递(call by value),但是java的方法参数中有两种类型——基本数据类型和对象引用。 其实当传递object时,有人认为是call by reference,其实传递的是这个reference的副本
- 传递基本数据类型时,不改变原变量的内容和地址
例子如下:
public class TestCall {
static void add(int a,int b){ //测试值传递
a = a+1;
b=3;
System.out.println("方法中的a: "+a);
}
public static void main(String []args){
int a = 0;
int b=0;
add(a,b);
System.out.println("a: "+a);
System.out.println("b: "+b);
}
}
输出:
方法中的a: 1
a: 0
b: 0
- 传递对象和引用类型时,不改变原变量的地址,但可以改变原变量的内容
因为当副本的引用改变时,原变量的引用并没有改变,但副本引用指向的是原变量的地址空间,所以当副本改变内容时,原变量的内容就发生了改变。
关于String
String与int 的相互转换的方法
1、String转化成int
int i = Integer.parseInt(String); //1
int i = Integer.valueOf(s).intValue();//2
注意:字符串转化为Double、Float或Long的方法大同小异
第一种方法直接使用静态方法,不产生多余对象,但会抛出异常
第二种方法中Integer.valueOf(s)相当于new Integer(Integer.parseInt(s)),也会抛出异常,但会多产生一个对象
2、int转化为String
String s = String.valueOf(i); //1
String s = Integer.toString(i);//2
String s = ""+i;//3
注意:Double、Float或Long转化为字符串的方法大同小异
第三种方法会产生两个对象,第一种方法直接使用String的静态方法,不会产生多余的对象
创建String对象的两种方法
到底创建几个对象,两种方法的区别?
首先来看一个例子:
public class TestCall {
public static void main(String []args){
String a = "abc";//1
String b= a;//2
String c = "abc"; //3
String d = new String("abc");//4
String e = new String("eee");//5
//比较引用指向的地址空间是否相等,即字符串所在的地址
System.out.println(a==b); //true
System.out.println(a==c); //true
System.out.println(a==d); //false
//比较引用指向的空间的内容是否相等,即字符串的内容
System.out.println(a.equals(b)); //true
System.out.println(a.equals(c)); //true
System.out.println(a.equals(d)); //true
/*说明:
* 1、创建了一个字符串常量对象“abc”。
* 具体:创建String对象的引用a,检查常量池里有没有“abc”,
* 发现没有,把“abc”放入常量池中,把引用指向这一块空间
*
* 2、赋值操作。创建String对象的引用b,将b指向a所指向的地址,即常量池中的“abc”
*
* 3、没有创建对象。创建String对象的引用c,检查常量池里有没有“abc”,
* 发现有,直接把引用指向这一块空间
*
* 以上三个步骤后,a b c其实都是指向字符串常量池中的“abc”
*
* 4、new创建了一个对象。创建String对象的引用d,检查常量池里有没有“abc”,
* 如果有,用new在堆中申请一块空间,将字符串池中的“abc”复制给该空间,再把引用d指向该空间
*
* 5、创建了两个对象,一个是字符串常量“eee”,一个是new创建的。
* 具体:创建String对象的引用d,检查字符串常量池里有没有“abc”,
* 发现没有,首先在堆中申请一块匿名空间存放“eee”字符串对象,
* 然后new String把这个字符串对象拷贝一份到堆中,返回该字符串对象(把引用d指向堆中的字符串),
* 此时那块匿名空间成了内存垃圾
*
* 4、5说明使用new创建字符串时,可能会创建1或2个对象
*
* */
}
}
注意:关于产生几个对象的进一步说明
再看一个例子:
String str = “a”+”b”;产生几个对象?
答案是3个,字符串常量区存储”a”,”b”,”ab”三个对象
String str = “a”+new String(“b”);产生几个对象?
答案是3个,字符串常量区存储”a”,”b”,堆中存储new String(“b”)的对象。
声明:
只有使用引号包含文本的方式创建的String对象之间使用“+”连接产生的新对象才会被加入字符串池中。对于所有包含new方式新建对象(包括null)的“+”连接表达式,它所产生的新对象都不会被加入字符串池中
堆还是栈?
上面这个例子并没有说明String str是存放在堆中还是栈中。
事实上,关键点是区分str是成员变量还是局部变量,如果是局部变量在方法体内,它就存储在栈中,如果是成员变量那么就跟随成员对象存储在堆中。
比如:
class A{
String str = new String("abc") // str是成员变量,存放堆中
public void getA(){
return str;
}
}
class A{
public void getA(){
String str = new String(“abc”) //str是局部变量,存放栈中
return str;
}
}
总结
对于基本类型参数,在方法体内对参数进行重新赋值,并不会改变原有变量的值。
对于引用类型参数,在方法体内对参数进行重新赋予引用,并不会改变原有变量所持有的引用。
方法体内对参数进行运算,不影响原有变量的值。
方法体内对参数所指向对象的属性进行操作,将改变原有变量所指向对象的属性值。
也就是说,对于基本数据类型,实现的是传值,只是个形参,不会改变原有值。对于引用数据类型,对这个引用进行操作,其实也是相当于对形参的操作,不会改变原来的引用。但是,当对这个引用的属性进行操作的时候,相当于CPP中的传址调用,可以改变这个引用的属性的值。
举个例子:
public class Main {
private static void getMiddleOne(boolean b, Boolean boo, Boolean[] arr){
b = true; //形参,不会改变原有值
boo = new Boolean(true); //引用变量的直接操作相当于值传递,不会改变原来的引用变量
arr[0] = true; //引用变量的属性的操作,会改变原有引用的属性,相当于传址调用
}
//测试
public static void main(String[] args) {
boolean b = false;
Boolean boo = new Boolean(false);
Boolean[] arr = new Boolean[]{false};
getMiddleOne(b, boo, arr);
System.out.println(b);
System.out.println(boo.toString());
System.out.println(arr[0]);
/**
* output:
* false
* false
* true
*/
}
}
创建了几个对象的深入探讨
(可参考文章:String,到底创建了多少个对象?)
更多的了解
Java中String类两种实例化的区别(图解)
JAVA中String类以形参传递到函数里面,修改后外面引用不能获取到更改后的值
在java方法中改变传递的参数的值
String,到底创建了多少个对象?