• Java编程陷阱-类成员初始化


    原文地址:http://blog.csdn.net/veryitman/article/details/6450523

    如果你忽略Java的细节,恐怕你的代码会充满bug,下面讨论关于类成员初始化问题。

    第一类,初始化成员变量在构造方法之前

    主要参考TIJ中的代码,来说明问题!!

    1. 新建一个类Tag

    package mark.initial;
    public class Tag {
        
        /**
         * 构造方法
         * 
         * @param maker
         */
        public Tag(int maker) {
            System.out.println("tag(" + maker + ")");
        }
    }

    2. 新建一个类Card

    package mark.initial;
    public class Card {
        
        Tag tag1 = new Tag(1);
        
        /**
         * 构造方法
         */
        public Card() {
            System.out.println("card()");
            Tag tag3 = new Tag(33);
        }
        
        Tag tag2 = new Tag(2);
        
        /**
         * 成员方法
         */
        public void f() {
            System.out.println("f()");
        }
        
        Tag tag3 = new Tag(3);
    }

    3. 新建测试类

    package mark.initial;
    public class TestInitial {
        
        public static void main(String[] args) {
            Card c = new Card();
            c.f();
        }
    }

    看结果之前,简单分析一下。在Card中,到处都有Tag对象,看起来比较乱。其实这是故意的,我们知道类的成员变量会被初始化为默认值比如引用初始化为null,int默认为0,float默认为0.0等,如果你没有指定这些成员变量的值时。

    在测试类TestInitial中,new一个Card,这样就会初始化它的成员变量tag1、tag2、tag3先为null,(由于我们手动将这些成员变量赋予新值即new该对象),然后会指向堆里面的对象。最后Card调用自己的构造函数,所以结果如下所示:

    tag(1)
    tag(2)
    tag(3)
    card()
    tag(33)
    f()

    第二类,成员变量初始化在构造方法之后

    子类成员变量初始化,父类Linux,子类User重写父类的print()方法,并在该方法中改变成员变量name值。

    package my.test;
    
    public class Linux {
        int size = 10;
        String name = "Linux";
    
        /**
         * 构造方法
         */
        public Linux() {
            System.out.println("I'm Linux OS!");
            print();
        }
    
        public void print() {
            System.out.println("父类Linux--print()");
        }
    }
    
    class User extends Linux {
        String name = "ubuntu";
    
        @Override
        public void print() {
            name = "ubuntu10.10";
            System.out.println("子类User--print()");
        }
    }

    测试代码:

    class TestLinux {
        
        public static void main(String[] args) {
            User user = new User();
            System.out.println(user.name);
        }
    }

    在main方法中创建User实例对象user,User会调用自己的构造方法,然而我们知道子类在其构造方法中会先调用父类的构造方法,这样一来User的父类Linux会执行自己的构造方法,从而调用print()方法,由于子类User覆写父类方法,所以调用的是子类的print()方法。将name的值变为ubuntu10.10,构造方法执行完毕,开始初始化User的成员变量即name为ubuntu,那么打印结果就是如下所示:

    I'm Linux OS!
    子类User--print()
    ubuntu

    第三类,其实这是第二类的另一个实例

    下面是单例模式:

    package my.test;
    public class ClassloadTest {
        // 声明类成员变量并创建该对象
        static final ClassloadTest test = new ClassloadTest();
        
        public static int a = 5;
        public static int b = 8;
        
        /**
         * 私有构造方法
         */
        private ClassloadTest() {
            a++;
            b++;
        }
        
        /**
         * 获取类的实例对象
         * 
         * @return ClassloadTest实例对象
         */
        public static ClassloadTest getInstance() {
            return test;
        }
    }

    好了,看懂上述代码之后,看看测试代码:

    class Test {
        public static void main(String[] args) {
            System.out.println("a = " + ClassloadTest.getInstance().a);
            System.out.println("b = " + ClassloadTest.getInstance().b);
        }
    }

    在公布答案之前,大部分人都会很自信的说结果是下面的样子:

    a = 6
    b = 9

    呵呵,你太冲动啦!!!回答错误,好好想一想吧!!

    下面这句代码是创建ClassloadTest 对象 test:

    // 声明类成员变量并创建该对象
        static final ClassloadTest test = new ClassloadTest();

    在测试类中,获得类的实例即创建对象,就会调用构造方法,其实发生的时间是这样子的:

    <1> 初始化test为null,a=0,b=0,这里的值是默认赋值,不是你手动所赋的值5和8

    <2> test = new ClassloadTest(),会调用构造方法,从而使a=1,b=1

    <3> 顺序执行代码,静态变量a=5,b=8,那么原来的a=1,b=1就被覆盖掉

    ok,执行结果应该是:

    a = 5
    b = 8

    如果,改变代码中static final ClassloadTest test = new ClassloadTest();的位置结果会不一样的。现在改变ClassloadTest类如下:

    package my.test;
    public class ClassloadTest {
        
        public static int a = 5;
        public static int b = 8;
        
        // 声明类成员变量并创建该对象
        static final ClassloadTest test = new ClassloadTest();
        
        /**
         * 私有构造方法
         */
        private ClassloadTest() {
            a++;
            b++;
        }
        
        /**
         * 获取类的实例对象
         * 
         * @return ClassloadTest实例对象
         */
        public static ClassloadTest getInstance() {
            return test;
        }
    }

    还是上使用上面的测试方法,结果如下:

    a = 6
    b = 9

    分析如下:

    <1> 初始化test为null,a=0,b=0,这里的值是默认赋值,不是你手动所赋的值5和8

    <2> a=5,b=8

    <3> test = new ClassloadTest(),会调用构造方法,从而使a=6,b=9

    小结:

    说到这里,我们至少明白下面的道理(针对类成员变量):

    <1> 面向对象编程,也需要考虑声明变量的顺序

    <2> Java中的声明和初始化不是原子操作,即他们不是一体化的,也就是说声明后它会有一个默认值,初始化值是可以手动赋值的。

  • 相关阅读:
    HDU3625(SummerTrainingDay05-N 第一类斯特林数)
    HDU3359(SummerTrainingDay05-I 高斯消元)
    HDU2157(SummerTrainingDay05-F dp)
    HDU4565(SummerTrainingDay05-C 矩阵快速幂)
    LOJ1070(SummerTrainingDay05-B 矩阵快速幂)
    SPOJ7001(SummerTrainingDay04-N 莫比乌斯反演)
    POJ3090(SummerTrainingDay04-M 欧拉函数)
    POJ1284(SummerTrainingDay04-K 原根)
    POJ2478(SummerTrainingDay04-E 欧拉函数)
    BZOJ3884(SummerTrainingDay04-C 欧拉定理)
  • 原文地址:https://www.cnblogs.com/cRaZy-TyKeIo/p/3457469.html
Copyright © 2020-2023  润新知