在编码过程中,如果你经常使用Intellij IDEA中的抽取方法操作(Ctrl+Alt+M)对某个模块进行代码抽取,那么要小心无形之中的引用传递引发的bug。

  首先我们要说明什么是值传递,什么是引用传递。
  在Java中基本类型以及他们的包装类,包含String类,都是值传递。也就是说,在构造一个处理参数的函数时,如果传入了这些类型的句柄当做函数的输入参数引用,在函数内部对于输入参数引用进行重新赋值,不会影响外部原参数的值。看以下代码:

    public static void main(String[] args) {
        String origin = "ORIGIN";
        int i = 1;
        Integer integer = 2;

        getString(origin);
        System.out.println(origin);
        System.out.println("origin out method:" + System.identityHashCode(integer));
        System.out.println("-------------------------------------");
        getNumber(i);
        System.out.println(i);
        System.out.println("-------------------------------------");
        getInteger(integer);
        System.out.println(integer);
        System.out.println("integer out method:" + System.identityHashCode(origin));
    }

    private static Integer getInteger(Integer integer) {
        integer = 20;
        System.out.println("integer in method:" + System.identityHashCode(integer));
        return integer;
    }

    private static int getNumber(int i2) {
        i2 = 10;
        return i2;
    }

    private static String getString(String origin) {
        origin = "TEST CHANGE";
        System.out.println("origin in method:" + System.identityHashCode(origin));
        return origin;
    }

    //origin in method:558638686
    //ORIGIN
    //origin out method:1149319664
    //-------------------------------------
    //1
    //-------------------------------------
    //integer in method:2093631819
    //2
    //integer out method:2074407503

  可以看到,尽管将三个参数作为输入参数放入方法中进行了重新的赋值,但最终方法外部打印出来的值还是原先的值。方法体外和引入方法体内的引用identityHashCode是不同的,所以可以认为一旦引入某个方法体,它就已经会被分配新的内存,是一个全新的版本。

  但是对于对象,数组这一类型的数据,通常是引用传递,即传入的是该类型数据的地址或地址相关的字段,可以看看以下代码:

    class Father {
        private Integer age;
        private String name;

        public Integer getAge() {
            return age;
        }

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

        public String getName() {
            return name;
        }

        public void setName(String name) {
            this.name = name;
        }

        @Override
        public String toString() {
            return "Father{" +
                    "age=" + age +
                    ", name='" + name + '\'' +
                    '}';
        }
    }

    public static void main(String[] args) {
        int[] intArray = {1, 2, 3};
        String[] stringArray = {"origin1", "origin2", "origin3"};
        Father father = new TestObjectAddress().new Father();

        changeIntArray(intArray);
        System.out.println(Arrays.toString(intArray));
        System.out.println(System.identityHashCode(intArray));
        System.out.println("---------------------------------");

        changeStringArray(stringArray);
        System.out.println(Arrays.toString(stringArray));
        System.out.println(System.identityHashCode(stringArray));
        System.out.println("---------------------------------");

        changeObject(father);
        System.out.println(father.toString());
        System.out.println(System.identityHashCode(father));
    }

    private static void changeObject(Father father) {
        if (father != null) {
            father.age = 50;
        }
        System.out.println(System.identityHashCode(father));
    }

    private static void changeStringArray(String[] stringArray) {
        if (stringArray != null && stringArray.length > 0) {
            stringArray[0] = "changed1";
        }
        System.out.println(System.identityHashCode(stringArray));
    }

    private static void changeIntArray(int[] intArray) {
        if (intArray != null && intArray.length > 0) {
            intArray[0] = 10;
        }
        System.out.println(System.identityHashCode(intArray));
    }

    //558638686
    //[10, 2, 3]
    //558638686
    //---------------------------------
    //1149319664
    //[changed1, origin2, origin3]
    //1149319664
    //---------------------------------
    //2093631819
    //Father{age=50, name='null'}
    //2093631819

  注意,System.identityHashCode(Object obj)这个方法,如果两个对象的结果不一样,那么就可以肯定它们不是一个对象,但两个对象的计算结果一样,也不能完全证明他们是一样的,但是这个碰撞的概率还是很小,我们这里不是很准确,但是这个结论还是可靠的(你可以使用obj1==obj2来判断两个对象是否相同)。当类型是数组(包含基本类型的数组),对象被当作参数传入一个方法之后,那么这个传入的是它的地址或者地址相关的参数,此时如果在方法体中对其内部的变量做出任何修改,方法执行完后,外部的对象也会受影响。也就是说,我们在方法体内处理的就是这个对象本身。

  对于向上转型和向下转型,其实我们也可以用实验代码来看看,内部的引用到底是怎么变化的。看如下代码:

    class Son extends Father {
    }

    class GrandSon extends Son {
    }

    class Father {
        private Integer age;
        private String name;

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

    public static void main(String[] args) {
        testCast();
    }

    private static void testCast() {
        GrandSon grandSon = new TestObjectAddress().new GrandSon();
        Son son = (Son) grandSon;
        Father father = (Father) son;
        System.out.println("--------向上转型--------");
        System.out.println(System.identityHashCode(grandSon) + "----" + grandSon);
        System.out.println(System.identityHashCode(son) + "----" + son);
        System.out.println(System.identityHashCode(father) + "----" + father);
        System.out.println("--------向上转型后再向下转型--------");
        Son anotherSon = (Son) father;
        System.out.println(System.identityHashCode(anotherSon));
        System.out.println("--------向下转型--------");
        Father daddy = new TestObjectAddress().new Father();
        try {
            Son daddySon = (Son) daddy;
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    //--------向上转型--------
    //558638686----com.algorithm.learn.test.unit3.address.TestObjectAddress$GrandSon@214c265e
    //558638686----com.algorithm.learn.test.unit3.address.TestObjectAddress$GrandSon@214c265e
    //558638686----com.algorithm.learn.test.unit3.address.TestObjectAddress$GrandSon@214c265e
    //--------向上转型后再向下转型--------
    //558638686
    //--------向下转型--------
    //java.lang.ClassCastException: com.algorithm.learn.test.unit3.address.TestObjectAddress$Father cannot be cast to com.algorithm.learn.test.unit3.address.TestObjectAddress$Son
    //    at com.algorithm.learn.test.unit3.address.TestObjectAddress.testCast(TestObjectAddress.java:40)
    //    at com.algorithm.learn.test.unit3.address.TestObjectAddress.main(TestObjectAddress.java:23)

  可以看到,所谓的向上转型,其实引用过去还是这个对象本身,但这个引用只能按照被修饰的类型进行方法调用,即父类是不能调用子类的方法的,虽然这个引用其实就是这个对象,但表现出来还是得遵循被修饰类的特性。也就比较好理解向上转型之后,再进行向下转型是可以的,因为这就是这个对象本身。但如果一开始new的就是父类,这个时候是没办法进行向下转型的,因为类型已经发生了变化。

 

 

 

posted on 2021-08-03 20:11  长江同学  阅读(239)  评论(0编辑  收藏  举报