以下是本文的目录大纲:
一.形参和实参简述
二.基本数据类型变量的传值
三.引用简述
四.引用的实例说明
五.总结
说到java中调用函数的参数引用,就要先说一下形参和实参,以便于更好地理解参数引用。
一.形参和实参简述
形参
- parameter:形式参数(形参)
- 形参出现在函数定义中,在整个函数体内都可以使用,离开该函数则不能使用
实参
- argument:实际参数(实参)
- 实参拥有明确的值,出现在主调函数中,值传给被调函数的形参
语法和语义
- syntax:语法:词语的用法
- semantic:语义:词语或语句的含义
举例一个语言的例子说明语法和语义:
1) “Lua java的是编写”,这句子你一定看不懂,因为它语法错误,不符合汉语的语法。
2) “Lua是Java编写的”,这句子语法正确,但是语义错误。
3) “Lua是C编写的”, 这句子,语法和语义都对。
功能
- 形参和实参的功能是用于函数调用时的数据传送。
区别
- 因为形参只有在函数内部有效,所以形参变量只有在被调用时才分配内存单元,在调用结束即刻释放所分配的内存单元。
- 实参和形参在数量上,类型上,顺序上应严格一致, 否则会发生“类型不匹配”的错误。
- 实参可以是常量、变量、表达式、函数等, 无论实参是何种类型的量,在进行函数调用时,它们都必须具有确定的值, 以便把这些值传送给形参。 因此应预先用赋值,输入等办法使实参获得确定值。
- 当形参和实参不是指针类型时,在该函数运行时,形参和实参是不同的变量,他们在内存中位于不同的位置,形参将实参的内容复制一份,在该函数运行结束的时候形参被释放,而实参内容不会改变。
- 如果函数的参数是指针类型变量,在调用该函数的过程中,传给函数的是实参的地址,在函数体内部使用的也是实参的地址,即使用的就是实参本身。所以在函数体内部可以改变实参的值。
二.基本数据类型变量的传值
public class TestParam {
public TestParam() {
// 基本数据类型的测试
int tempParam = 1;
boolean flag = true;
System.out.println("********原始值(实参值)********");
System.out.println("tempParam=:" + tempParam);
System.out.println("flag=:" + flag);
System.out.println("********调用方法时(形参值)********");
increseParam(tempParam);
updateBoolean(flag);
System.out.println("********调用方法后(实参值)********");
System.out.println("tempParam=:" + tempParam);
System.out.println("flag=:" + flag);
}
public int increseParam(int tempParam) {
tempParam++;
System.out.println("increseParam方法=:" + tempParam);
return tempParam;
}
public boolean updateBoolean(boolean flag) {
flag = !flag;
System.out.println("updateBoolean方法=:" + flag);
return flag;
}
public static void main(String[] args) {
new TestParam();
}
}
运行结果:
********原始值(实参值)********
tempParam=:1
flag=:true
********调用方法时(形参值)********
increseParam方法=:2
updateBoolean方法=:false
********调用方法后(实参值)********
tempParam=:1
flag=:true
从以上结果可看出,虽然在 updateBoolean(boolean) 方法中改变了传进来的参数的值,但对这个参数源变量本身并没有影响,即对TestParam()方法里的 flag变量没有影响。以参数形式传递基本数据类型的变量时,实际上是将参数的值作了一个拷贝传进方法函数的,那么在方法函数里再怎么改变其值,其结果都是只改变了拷贝的值,而不是源值。从而说明,参数类型是基本数据类型的时候,形参不会改变实参的值,而且是按实际值传递的。
三.引用简述
Java 是传值还是传引用,问题主要出在对象的传递上,因为 Java中基本数据类型没有引用。
简单的说,引用其实就像是一个对象的名字或者别名(alias),一个对象在内存中会请求一块空间来保存数据,根据对象的大小,它可能需要占用的空间大小也不等。访问对象的时候,我们不会直接是访问对象在内存中的数据,而是通过引用去访问。引用也是一种数据类型,我们可以把它想象为类似C 语言中指针的东西,它指示了对象在内存中的地址——只不过我们不能够观察到这个地址究竟是什么。
综上所述,可以得到一下观点:
- 引用是一种数据类型,保存了对象在内存中的地址,这种类型即不是我们平时所说的基本数据类型也不是类实例(对象);
- 不同的引用可能指向同一个对象,换句话说,一个对象可以有多个引用,即该类类型的变量。比如 String a = "Hello";
String b = a;
这里,a 和 b 是不同的两个引用,我们使用了两个定义语句来定义它们。但它们的值是一样的,都指向同一个对象"Hello",这就是不同的两个引用指向了共同的对象。
四.引用的实例说明
- String类型
public class TestParam {
public TestParam() {
// 基本数据类型的测试
String tempString = "TEST";
System.out.println("********原始值(实参值)********");
System.out.println("tempParam=:" + tempString);
System.out.println("tempString的哈希值:" + tempString.hashCode());
System.out.println("********调用方法时(形参值)********");
updateString(tempString);
System.out.println("********调用方法后(实参值)********");
System.out.println("tempParam=:" + tempString);
}
public String updateString(String tempStr) {
System.out.println("修改前tempStr的哈希值:" + tempStr.hashCode());
tempStr = tempStr + "_APPEND";
System.out.println("updateString方法" + tempStr);
System.out.println("修改后tempStr的哈希值:" + tempStr.hashCode());
return tempStr;
}
public static void main(String[] args) {
new TestParam();
}
}
运行结果:
********原始值(实参值)********
tempParam=:TEST
tempString的哈希值:2571410
********调用方法时(形参值)********
修改前tempStr的哈希值:2571410
updateString方法TEST_APPEND
修改后tempStr的哈希值:-271541401
********调用方法后(实参值)********
tempParam=:TEST
从以上结果可看出,虽然在 updateString(String) 方法中改变了传进来的参数的值,但对这个参数源变量本身并没有影响,即对TestParam()方法里的 tempString变量没有影响。为什么呢,因为修改前实参和形参的哈希值一样,而修改后形参的哈希值已经改变,所以tempStr只是改变了引用,而没有改变对象的值。那说明,参数类型是String类型的时候,形参不会改变实参的值,而且是按对象的地址传递的。这里看不明白的朋友,可以看看这个链接,有更详细的讲解:
- StringBuffer类型
public class TestParam {
public TestParam() {
// 基本数据类型的测试
StringBuffer string = new StringBuffer("Hello");
System.out.println("********原始值(实参值)********");
System.out.println("tempString=:" + string);
System.out.println("********调用方法时(形参值)********");
updateStringBuffer(string);
System.out.println("********调用方法后(实参值)********");
System.out.println("tempString=:" + string);
}
public static void updateStringBuffer(StringBuffer str) {
str.append(", World!");
System.out.println("updateStringBuffer方法:" + str);
}
public static void main(String[] args) {
new TestParam();
}
}
运行结果:
********原始值(实参值)********
tempString=:Hello
********调用方法时(形参值)********
updateStringBuffer方法:Hello, World!
********调用方法后(实参值)********
tempString=:Hello, World!
从以上结果可看出,在 updateStringBuffer(StringBuffer) 方法中改变了传进来的参数的值,而且改变了参数源变量本身的值,即对TestParam()方法里的 string变量有影响。因为StringBuffer类型的引用地址是无法改变的,所以str只是改变了引用地址。那说明,参数类型是StringBuffer类型的时候,形参会改变实参的值,而且是按对象的地址传递的。
- ArrayList<T>类型
import java.util.ArrayList;
public class TestParam {
public TestParam() {
ArrayList<String> list = new ArrayList<String>();
list.add("AAAAA");
list.add("BBBBB");
list.add("CCCCC");
System.out.println("********原始值(实参值)********");
System.out.println("size1=:" + list.size());
System.out.println("********调用方法时(形参值)********");
clearList(list);
System.out.println("********调用方法后(实参值)********");
System.out.println("size1=:" + list.size());
System.out.println();
System.out.println("**********************************");
ArrayList<String> list2 = new ArrayList<String>();
list2.add("AAAAA");
list2.add("BBBBB");
list2.add("CCCCC");
System.out.println("********原始值(实参值)********");
System.out.println("size2=:" + list2.size());
System.out.println("********调用方法时(形参值)********");
newList(list2);
System.out.println("********调用方法后(实参值)********");
System.out.println("size2=:" + list2.size());
}
public void clearList(ArrayList<String> claerlist) {
claerlist.clear();
System.out.println("clearList=:" + claerlist.size());
}
public void newList(ArrayList<String> newlist) {
newlist = new ArrayList<String>();
newlist.add("DDDDD");
System.out.println("newList=:" + newlist.size());
}
public static void main(String[] args) {
new TestParam();
}
}
运行结果:
********原始值(实参值)********
size1=:3
********调用方法时(形参值)********
clearList=:0
********调用方法后(实参值)********
size1=:0
**********************************
********原始值(实参值)********
size2=:3
********调用方法时(形参值)********
newList=:1
********调用方法后(实参值)********
size2=:3
从以上结果可看出,在 clearList(ArrayList<String>) 方法中改变了传进来的参数的值,而且改变了参数源变量本身的值,即对TestParam()方法里的 list变量有影响。由此看出ArrayList<String>类型的引用地址虽然没有改变,但是其引用对象的值已经发生了改变。而在 newList(ArrayList<String>) 方法中,通过newlist = new ArrayList<String>()首先改变了newlist的引用地址,再去改变newlist的值,所以对参数源变量没有影响。从而说明,参数类型是newList(ArrayList<String>)类型的时候,是按对象的地址传递的,形参不改变自身引用地址的情况下,会改变实参的值。
五.总结
Java中,改变参数的值有两种情况:
第一种,使用赋值号“=”直接进行赋值使其改变,这种情况,其改变不会影响到方法该方法以外的数据,或者直接说源数据。如基本数据类型(传实际值)和String类型(传对象的地址)的赋值;
第二种,对于某些对象的引用(对象的地址),通过一定途径对其成员数据进行改变,这种方法,会影响到源数据——因为引用指示的对象没有变,对其成员数据进行改变则实质上是改变的该对象。如StringBuffer类型、ArrayList<T>类型、数组、Map的赋值。