Java函数的参数传递引发的思考

今天偶尔看到一个讲Java函数中的参数传递相关问题的视频,虽然是一个很简单的程序,但是里面涉及到很多Java基础知识,有必要整理一下。

Java的函数参数传递到底是值传递还是引用传递?

明确概念

值传递:表示方法接收的是调用者提供的值。
引用传递:表示方法接收的是调用者提供的变量地址。

根据Horstmann的《java核心技术》可知,Java是没有引用传递的。

java程序设计语言总是采用值调用。也就是说,方法得到的是所有参数值的一个拷贝,特别是,方法不能修改传递给它的任何参数变量的内容。

有些程序员(甚至是本书的作者),认为java程序设计语言对对象采用的是引用调用,实际上这种理解是不对的。

关于Java的值传递大部分人认为,

  • 对于基本数据类型变量(int、long、double、float、byte、boolean、char),将原值拷贝一份传递,方法内的操作对原有的数据不会有影响。
  • 对于对象型变量,将指向地址的值拷贝一份传递,实质就是复制指向地址的指针。(String类型也是对象型变量)
    总而言之,不管参数的类型是什么,一律传递参数的副本。

传递基本类型

public class Swap {

	public static void main(String[] args) {
		Integer a = 10;
		System.out.println("swap before: a = "+a);
		swap(a);
		System.out.println("swap  after: a = "+a);
	}

	private static void swap(Integer a) {
		// TODO Auto-generated method stub
		a = 30;
	}
}

结果:

swap before: a = 10
swap  after: a = 10

结果还是不变的,方法内对参数操作不影响原数据。

传递对象类型

public class Swap {

	public static void main(String[] args) {
		Person p = new Person();
		p.setAge(20);
		System.out.println("update before: age = "+p.getAge());
		update(p);
		System.out.println("update  after: age = "+p.getAge());
	}

	private static void update(Person p) {
		p.setAge(30);
	}
}

class Person{
	private Integer age;

	public Integer getAge() {
		return age;
	}

	public void setAge(Integer age) {
		this.age = age;
	}
	
}

结果:

update before: age = 20
update  after: age = 30

结果变化了,对象参数是传入地址值,然后修改了对象中的值,地址仍不变化,但是值变了。

基于上述情况,如果写一个互换数据的函数就不能按照常规写法,以下是使用反射来互换

import java.lang.reflect.Field;

public class Swap {
	public static void main(String[] args) {
		// TODO Auto-generated method stub
		Integer a=10,b=15;
		System.out.println("swap before:a="+a+",b="+b);
		swap(a,b);
		System.out.println("swap  after:a="+a+",b="+b);	
	}

	private static void swap(Integer a, Integer b) {
		try {
			Field f = Integer.class.getDeclaredField("value");
			f.setAccessible(true);
			Integer temp = new Integer(a.intValue());
			f.set(a, b.intValue());
			f.set(b, temp);
		} catch (NoSuchFieldException e) {
			e.printStackTrace();
		} catch (SecurityException e) {
			e.printStackTrace();
		}catch (IllegalArgumentException e) {
			e.printStackTrace();
		} catch (IllegalAccessException e) {
			e.printStackTrace();
		}	
	}
}

结果:

swap before:a=10,b=15
swap  after:a=15,b=10

其中需要注意其中一句代码

Integer temp = new Integer(a.intValue());

这是新建一个对象,而不是直接赋予,为什么呢?

Java的Integer.java源码中有这样一段代码

 private static class IntegerCache {
        static final int low = -128;
        static final int high;
        static final Integer cache[];

        static {
            // high value may be configured by property
            int h = 127;
            String integerCacheHighPropValue =
                sun.misc.VM.getSavedProperty("java.lang.Integer.IntegerCache.high");
            if (integerCacheHighPropValue != null) {
                try {
                    int i = parseInt(integerCacheHighPropValue);
                    i = Math.max(i, 127);
                    // Maximum array size is Integer.MAX_VALUE
                    h = Math.min(i, Integer.MAX_VALUE - (-low) -1);
                } catch( NumberFormatException nfe) {
                    // If the property cannot be parsed into an int, ignore it.
                }
            }
            high = h;

            cache = new Integer[(high - low) + 1];
            int j = low;
            for(int k = 0; k < cache.length; k++)
                cache[k] = new Integer(j++);

            // range [-128, 127] must be interned (JLS7 5.1.7)
            assert IntegerCache.high >= 127;
        }

        private IntegerCache() {}
    }

即如果Integer的值在IntegerCache.lowIntegerCache.high之间([-127,128]),那么直接在IntegerCache里面获取,如果值大于这个范围则会新建一个Integer类型

例如若改成Integer temp = a.intValue()
则结果会是

swap before:a=10,b=15
swap  after:a=15,b=15

temp第一次赋值的之后是10,之后进行f.set(a, b.intValue());操作之后a变成15,因为是从IntegerCache拿值,导致这个时候temp变了,变成了15,最后导致结果出现偏差。所以需要避开从IntegerCache取值才能使得结果准确。

如果a、b超过那个范围,再看结果

swap before:a=231,b=144
swap  after:a=144,b=231

所以就需要自己重新新建对象才能正确。

你可能感兴趣的:(java,开发语言)