解析Java中的值传递

C++中在进行参数传递时,分为按值传递、引用传递(&)、按指针传递(*),需要自己指定参数传递类型。但JAVA表面上只有值传递,但真的只是值传递吗?其实并非如此,Java中也分为按值传递、按引用传递,只是传递类型按照传入参数的类型而定。

传递类型定义

1、值传递:在调用函数时将实际参数复制一份传递到函数中,这样在函数中如果对参数进行修改,修改的将是参数副本,将不会影响到实际参数。
2、引用传递:在调用函数时将参数的引用进行拷贝(即将实际参数的地址传递到函数中),那么在函数中对参数所进行的修改,其实修改的是参数本身,将影响到实际参数。
在这里插入图片描述

Java中的参数传递

1、当传入函数的参数是基本数据类型(byte,boolean,char,short,int ,float,double,long)时,此时参数传递进行的是按值传递。
2、当传入函数的参数是引用类型时(基本类型的包装类、自定义类、数组),此时参数传递进行的是引用传递。

引用数据类型

引用数据类型包括类(基本类型的包装类)、接口、数组。那它与基本类型有什么区别呢?
1、引用数据类型数据其实包含两部分,一是对象引用(reference),这部分存在栈得局部变量表中,它是指向对象的引用指针;另外一部分是对象本身,它存放在堆中(实际数据)。例如下文中的例子,deliveryType dt=new deliveryType();,dt是一个对象引用类型数据,存放在栈中;而这个对象本身存在堆中,dt指向堆中的对象。非常量基本数据类型变量直接存放在栈中。
2、引用数据类型数据在进行参数传递时,使用的是引用传递,传递进函数的其实就是对象的指针(地址),也就是对象引用类型变量,即上面的dt,而不是对象本身。在函数内拿到对象的地址,可以直接对堆中的对象进行值的修改,这也是它为什么可以修改实参的值的原因。

Java的值传递分析

public static void change_int(int a){
        a=222;
    }
 public static void change_double(double e){
        e=23.5;
    }    
public static void main(String[] args){
       int  a=243; 
       double e=23.4;
       change_int(a);
       change_double(e);
       System.out.println("a的值"+a);  //243  没有被修改
       System.out.println("e的值"+e);  //23.4 没有被修改
       }

通过代码实践发现,对于基本类型数据,将其传入函数后,在方法内改变参数的值,只是修改了形参(参数副本)的值,不会影响实参(原参数)的值。
它是将实参的值在该函数所属的栈帧的局部变量表中拷贝了一份,改变的只是局部变量表中形参的值,当函数执行完毕,该栈帧销毁,形参也销毁,不会影响实参值。

Java的引用传递分析

public class deliveryType {
    private int age;
     public static  int high=170;
    public int getAge() {
        return age;
    }
    public void setAge(int age) {
        this.age = age;
    }
    public void change_age(deliveryType dt){
        dt.setAge(19);
    }
    public void change_age2(deliveryType dt){
        dt=new deliveryType();
        dt.setAge(30);
    }
    public static void change_high(int high){
        high=232323;
    }
    public static void change_array(int[] array){
        array[3]=100;
    }
        public static void main(String[] args){
       int   []array={1,2,3,4,5};
       deliveryType dt=new deliveryType();
        dt.setAge(12);
        change_high(high);
        change_array(array);
        dt.change_age(dt);
        System.out.println("第一次修改之后的年龄"+dt.getAge()); //19
        dt.change_age2(dt);
        System.out.println("第二次修改之后的年龄"+dt.getAge()); //19
        System.out.println("身高的值"+high);  //170
        System.out.println("数组改完之后的值为"+array[3]); //100
    }
}

从以上代码以及结果发现,除第二次修改age没有修改实参成功(注意:有两种修改age的函数),以及修改high没有成功,其它修改实参成功。
1、修改high失败的原因:因为它是以high为变量传入(基本数据类型),而不是以对象dt传入(引用数据类型)。
2、第一次修改age成功的原因:是为传入的是对象的对象引用类型变量dt,传入的是引用数据类型,这里进行的是引用传递(将对象dt的引用变量传入,该引用变量的值为对象dt的地址值),所以可以在函数里直接对对象进行操作,修改堆中的对象dt的age值。
3、第二次修改age失败的原因是因为传入的引用变量dt本来指向对象dt,但它在函数中指向了新的对象,修改的也是新的对象中的age值,那原本对象中的age值没有被改变,自然修改失败。
4、数组值修改成功的原因:数组同对象一样,存储在堆中,传入函数的变量其实是一个对象引用类型变量,这个对象引用类型变量的值是数组的地址值。在函数中对数组进行改变,其实就相当于利用对象引用类型变量的值,到堆中对数组本身进行值得修改,所以实参修改成功。
综上所述,Java中引用数据类型之所以进行的是引用传递,是因为它传入的不是值本身,而是引用,也就是对象引用类型变量,它保存着指向堆中的对象的指针(地址)。所以在函数内对其进行改变,其实就是按着指针去到堆中对堆中的数据进行更改。所以实参的值也就改变了。

String、Integer、Double等包装类的参数传递类型分析

按理来说,这些包装类都是属于类,这种类型的变量属于引用类型数据,进行的是引用传递,但你会发现它却表现为值传递,这是为什么呢?

 public static void main(String[] args){ 
       Double  e=new Double(23.4);
       Integer d=new Integer(2342);
       String b="bbb";
       String c=new String("bbb");
        change_string(b);
        change_string(c);
        change_double(e);
        change_Integer(d);
        System.out.println("b的值"+b);//bbb
        System.out.println("c的值"+c);//bbb
        System.out.println("d的值"+d);//2342
        System.out.println("e的值"+e);//23.4
    }

    public static void change_Integer(Integer c){
        c=222222;
      //  c=new Integer(231321);
    }

    public static void change_string(String b){
         //b=new String ("更改");
          b="更改";
         //b=null;
    }
    public static void change_double(Double e){
        e=23.5;
    }

运行以上代码,你会发现,所有的对象的值,在函数内进行修改,但在main()中的变量值还是原值。这是因为它们都是常量类,类中的值是final变量,一经初始化就不可改变,在函数中进行修改时,若新值已存在,则将副本引用变量指向该新值存在的位置,否则在常量池中或者堆中,新建了一个实例,并将副本引用变量指向它。原来指向的对象中的数据没有改变。

String以及Integer等包装类都是final类(final类中的成员变量可以被定义为final或非final形式),但String类以及基本类型的包装类的所有得变量都被final修饰,都是常量,关键是value这个变量被修饰为常量,因此无法被改变,只能让对象引用变量进行重新指向。不改变原来对象得值,这也是String、Integer等引用类型变量表现为值传递的原因。
(下面节选String和Integer的源码)

public final class String implements Serializable, Comparable<String>, CharSequence {
    @Stable
    private final byte[] value;
    private final byte coder;
    private int hash;
    private static final long serialVersionUID = -6849794470754667710L;
    static final boolean COMPACT_STRINGS = true;
    private static final ObjectStreamField[] serialPersistentFields = new ObjectStreamField[0];
    public static final Comparator<String> CASE_INSENSITIVE_ORDER = new String.CaseInsensitiveComparator();
    static final byte LATIN1 = 0;
    static final byte UTF16 = 1;
public final class Integer extends Number implements Comparable<Integer> {
    public static final int MIN_VALUE = -2147483648;
    public static final int MAX_VALUE = 2147483647;
    public static final Class<Integer> TYPE = Class.getPrimitiveClass("int");
    static final char[] digits = new char[]{'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z'};
    static final byte[] DigitTens = new byte[]{48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 49, 49, 49, 49, 49, 49, 49, 49, 49, 49, 50, 50, 50, 50, 50, 50, 50, 50, 50, 50, 51, 51, 51, 51, 51, 51, 51, 51, 51, 51, 52, 52, 52, 52, 52, 52, 52, 52, 52, 52, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 54, 54, 54, 54, 54, 54, 54, 54, 54, 54, 55, 55, 55, 55, 55, 55, 55, 55, 55, 55, 56, 56, 56, 56, 56, 56, 56, 56, 56, 56, 57, 57, 57, 57, 57, 57, 57, 57, 57, 57};
    static final byte[] DigitOnes = new byte[]{48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57};
    static final int[] sizeTable = new int[]{9, 99, 999, 9999, 99999, 999999, 9999999, 99999999, 999999999, 2147483647};
    private final int value;
    public static final int SIZE = 32;
    public static final int BYTES = 4;
    private static final long serialVersionUID = 1360826667806852920L;

总结

1、当传入函数的参数是基本数据类型(byte,boolean,char,short,int ,float,double,long)时,此时参数传递进行的是按值传递,传入函数内的是值的拷贝。
2、当传入函数的参数是引用类型时(基本类型的包装类、自定义类、数组),此时参数传递进行的是引用传递,传入的是对象的对象引用的拷贝,该对象引用的值为对象的地址值。
3、基本类型的包装类以及String这些常量类,因为类中的值是被final修饰的,所以在函数内部无法进行值得修改,只能将对象引用重新指向新的对象(非原来对象),因此表现为值传递。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章