在C++里,当调用一个函数时,比如向函数传递一个vector,如果加上引用符号&,那么就是把这个vector本身传入函数,如果没有加引用符合,则是在函数里拷贝了一份vector。所以如果使用的是递归函数,那么每调用一次就拷贝了一次该数组,就很占空间,所以了解这个知识点十分重要。如这篇博客所述:vector作为形参时加引用与不加引用的区别
那么在java里这个知识点是怎样的呢? 比如我向一个函数传递一个ArrayList或者传递一个String,在函数里是拷贝了一份它们的值,还是传递的是它们本身?这将影响到在函数里对它们进行修改会不会影响到原本的值。
其实在Java中,函数参数的传递方式与C++有所不同,在java里参数传递分为值传递与引用传递。在Java中,所有对象类型(包括ArrayList和String)都是按引用传递的。
当你将一个对象作为参数传递给一个函数时,实际上传递的是对象的引用(类似于C++中的指针)。这意味着在函数内部,你可以通过引用修改对象的内容,并且这些修改将在函数外部可见。
例如,如果你将一个ArrayList作为参数传递给一个函数,并在函数内部修改该ArrayList,这些修改将影响到原始的ArrayList对象。这是因为实际上传递的是对ArrayList对象的引用,而不是对它的拷贝。
public static void main(String[] args) {
ArrayList list = new ArrayList<>();
list.add(1);
list.add(2);
list.add(3);
System.out.println("Before: " + list); // 输出:Before: [1, 2, 3]
modifyList(list);
System.out.println("After: " + list); // 输出:After: [1, 2, 3, 4]
}
public static void modifyList(ArrayList list) {
list.add(4);
}
但是要注意,对于引用类型参数,在方法体内对参数进行重新赋予引用,并不会改变原有变量所持有的引用:
public static void main(String[] args) {
ArrayList list=new ArrayList<>();
list.add(1);
System.out.println(list.get(0));//输出1
modifyList(list);
System.out.println(list.get(0));//输出1
}
public static void modifyList(ArrayList list){
list=new ArrayList<>();
list.add(2);
}
在Java中,基本数据类型(如int、double、boolean等)是按值传递的。当你将基本数据类型作为参数传递给一个函数时,函数会接收到该值的一个副本,而不是原始值本身。这意味着在函数内部对参数进行修改不会影响原始的变量。而在Java中,数组类型则被视为对象,因为数组对象是对一组相同类型的元素的封装,并提供了一些方法和属性来操作和访问这些元素。所以Java中的数组类型也是按引用传递的,函数内部的修改会影响原始的数组对象。
public static void main(String[] args) {
int num = 5;
System.out.println("Before: " + num); // 输出:Before: 5
modifyValue(num);
System.out.println("After: " + num); // 输出:After: 5
int[] array = {1, 2, 3};
System.out.println("Before: " + Arrays.toString(array)); // 输出:Before: [1, 2, 3]
modifyArray(array);
System.out.println("After: " + Arrays.toString(array)); // 输出:After: [1, 2, 4]
}
public static void modifyValue(int value) {
value = 10;
}
public static void modifyArray(int[] arr) {
arr[2]=4;
}
特殊情况是关于不可变类型,Java中有一些内置的不可变类。这些类的对象一旦创建,就不能被修改。比如String,当函数尝试修改传递进来的String对象,但实际上String是不可变的,所以函数内部的修改只是创建了一个新的String对象,并不影响原始的String对象:
public static void main(String[] args) {
String str = "Hello";
System.out.println("Before: " + str); // 输出:Before: Hello
modifyString(str);
System.out.println("After: " + str); // 输出:After: Hello
}
public static void modifyString(String str) {
str += " World";
}
常见的不可变类包括:String、Integer、Double、Boolean等包装类、Immutable集合类(例如,Collections.unmodifiableList()方法返回一个不可修改的List,Map.of()方法返回一个不可修改的Map)。
public static void main(String[] args) {
Double i=1.0;
System.out.println(i); // 1.0
modifyDouble(i);
System.out.println(i); // 1.0
}
public static void modifyDouble(Double i) {
i+=1.5;
}
List list = Collections.unmodifiableList(Arrays.asList("a", "b", "c"));
list.add("d"); // UnsupportedOperationException
不可变类的设计有许多优点,包括线程安全、安全性、缓存等。通过确保对象的不可变性,可以避免许多潜在的问题,并提供更好的性能和可靠性。需要注意的是,虽然不可变类本身的值无法更改,但是可以通过创建新的对象来表达不同的值。例如,在字符串拼接时,实际上是创建了一个新的字符串对象。因此,尽管不可变类的对象本身是不可修改的,但可以通过创建新的对象来表达不同的状态或值。
针对引用传递,如果不想改变原始值,又想在函数里对参数进行修改则可以创建一个新的对象来表示修改后的结果,即拷贝一份副本防止修改原值。