随笔-95  评论-31  文章-10  trackbacks-0
最近一直在看设计模式和数据结构,把java面向对象的一些细节一些容易出错的地方。唉 遗忘了 我觉得造成这种后果的原因是:
在做项目的时候容易形成那种习惯性的东西,有时候这种习惯会影响到自己对java语法的一些理解(如果不经常温习基础知识,有时候会让自己的习惯潜移默化或者改变自己原本正确的认识,或者渐渐模糊了一些最基础的东西) ,不说这些了,我之前自认为面向对象基础那一块是很扎实的,但我觉得我还是浮躁了!
这里我要重新认识和总结下static final super this 以及实例变量、类变量的初始化时机以及初始化的顺序,以及多重继承的时候调用父类的隐式构造和显示构造这些东西,如果里面在有一些赋值应该怎样怎样?还是以代码来说话,首先先说明实例变量的初始化时机,先不提继承的东西
package com.lx;

public class Cat
{
    String name;
    int age;

    public Cat(String name, int age)
    {
        System.out.println("执行构造器");
        this.name = name;
        this.age = age;
    }

    {
        System.out.println("执行非静态初始化块");
        weight = 2.0;
    }
    double weight = 2.3;

    public String toString()
    {
        return "Cat[name=" + name + ",age=" + age + ",weight=" + weight + "]";
    }
    
    public static void main(String[] args)
    {
        Cat cat1 = new Cat("first",2);
        System.out.println(cat1);
        Cat cat2 = new Cat("second",3);
        System.out.println(cat2);
    }
   
}
这里要说明的问题是:实例变量的初始化以及赋值时机
实例变量有3个地方执行实例化:
1、定义实例变量时候指定初始值
2、非静态初始化块中对实例变量指定初始值
3、构造器中对实例变量指定初始值。
关键点:当创建对象new的时候,定义实例变量(非static)赋值、非静态初始化块赋值,这两种赋值总是位于构造器的赋值之前!同时变量赋值、非静态初始化块赋值之间的顺序是在源代码中写的顺序。(记住这句话就不会出错了!)

其次说明类变量的初始化以及赋值时机,看如下代码:
package com.lx;

public class Price
{
    final static Price INSTANCE = new Price(2.8);
    static double initPrice = 20;
    double currentPrice;
    public Price(double discount){
        currentPrice = initPrice - discount;
    }
    public static void main(String[] args)
    {
        System.out.println(Price.INSTANCE.currentPrice);
        Price p = new Price(2.8);
        System.out.println(p.currentPrice);
    }
}
输出结果:-2.8和17.2
类变量的初始化以及赋值时机:
1、定义类变量时候指定初始值。
2、静态初始化块中对类变量指定初始值。
首先要明确:无论new多少个Price实例,static的变量都只初始化赋值(如果有赋值的话)一次,要点:static的变量只有在使用的时候即第一次调用它的时候(而不管它是否new过),才会开始初始化并赋值,顺序是:系统为该类的static类变量分配内存空间。按初始化代码的排列顺序对类变量进行初始化。
所以上面代码,调用Price.INSTANCE.currentPrice的时候,系统开始为static的变量初始化并开始赋值,按顺序执行,那么INSTANCE=new Price(2.8);是赋值,然而initPrice还没有赋值,仅仅只是分配了内存空间,因为是按初始化代码的顺序对类变量进行初始化。所以相减为0-2.8=-2.8。

隐式调用和显示调用

当调用某个类的构造器来创建Java对象时,系统总会先调用父类的非静态初始化块进行初始化,然后调用父类的构造器,这个是隐式调用(缺省是无参构造),如果显示调用就是super()根据super里面不同的参数去调用对应的构造,但是在构造里面又遵循这个原则,先调用父类的非静态初始化块,然后父类的构造器。。。一直到Object。所以看下面的代码:
package com.lx;

public class Creature
{
    {
        System.out.println("Creature的非静态初始化块");
    }

    public Creature()
    {
        System.out.println("Creature无参数的构造器");
    }

    public Creature(String name)
    {
        System.out.println("Creature有参的构造器参数为:" + name);
    }
    public static void main(String[] args)
    {
        new Wolf(5.6);
    }
}

class Animal extends Creature
{
    {
        System.out.println("Animal的非静态初始化块");
    }

    public Animal(String name)
    {
        super(name);
        System.out.println("Animal带一个有参数的构造器,name参数为:" + name);
    }

    public Animal(String name, int age)
    {
        this(name);
        System.out.println("Animal带2个参数的构造器,其age:" + age);
    }
}

class Wolf extends Animal
{
    {
        System.out.println("wolf的非静态初始化块");
    }

    public Wolf()
    {
        super("灰太狼", 3);
        System.out.println("Wolf无参数的构造器");
    }

    public Wolf(double weight)
    {
        this();
        System.out.println("Wolf的带weight参数的构造器,weight参数:" + weight);
    }
}
理解了上面那句话对于这段代码的输出也就不会觉得难理解了。同时super和this不能同时出现

最后一个关键点很重要:编译时类型调用实例变量其值是编译时的变量值,编译时类型调用方法的时候其值调用的是实际类型的方法!如何理解?就是这样比如
父类 父 = new 子类();//父和子都有int a这个变量
父.a  //那么这里其实是父的a变量的值  即编译时类型即调用的是父的变量值
父.方法();//那这里其实是子类的重写方法,即运行时类型是子类。
当调用子类构造的时候,不管隐式调用还是显示调用,当调用父类构造的时候父类构造里面出现this的时候,这个this一定是子类类型的!(规律) 但是编译时类型是父类的,如果里面调用父类的变量,那么其实是父类的变量,而不是子类的变量,虽然它实际类型是子类
代码如下:
package com.lx;

public class Animal1
{
    private String desc;

    public Animal1()
    {
        this.desc = getDesc();
    }

    public String getDesc()
    {
        return "Animal";
    }

    public String toString()
    {
        return desc;
    }
    
    class Wolf1 extends Animal1{
        private String name;
        private double weight;
        public Wolf1(String name,double weight){
            this.name = name;
            this.weight = weight;
        }
        @Override
        public String getDesc()
        {
            return "Wolf[name="+name+",weight="+weight+"]";
        }
    }
    
    public static void main(String[] args)
    {
        System.out.println(new Animal1().new Wolf1("灰太狼",32.3));
    }
    
}
根据上面说的原则,这里调用new Wolf1(“灰太狼”,32.3)的时候里面没有super显示调用父类构造,那么就会是隐式调用父构造即父无参构造,而父无参构造里面的this.desc=getDesc();
其中给getDesc()前面加上this,就是this.desc = this.getDesc();这里的this其实是Wolf1的类型,可以打印输出System.out.println(this.getClass());输出的是Wolf1类型,那么关键点就来了:这里的this实际上对于desc变量来说是编译时类型的变量,那么会直接调用编译类型的变量即这里的desc,然而对于方法来说:这里this.getDesc()其实调用的运行时类型的方法。然而这个时候name和weight分别只分配了内存空间null和0.0所以最后结果就是这样。
通过javap工具可以清楚的看到里面运行时候变量的分配情况,
最后一点,如果父类和子类有同名的成员变量,有重写的方法,java的编译器在处理方法和成员变量的时候存在区别:
1、父类的变量不会转移到子类去,父类和子类仍然有各自的同名的成员变量
2、父类的方法会转移到子类中去,如果子类也包含了相同的方法,那么父类的方法不会转移到子类中去,这个时候子类的方法就构成了重写。
再理解:父类和子类同名的成员变量,子类只是对父类的成员变量做了隐藏,内存中仍然为父类的同名变量开辟了空间。如下代码:
package com.lx;

public class Parent
{
    public String tag = "java编程";
    public static void main(String[] args)
    {
        Son s = new Son();
//        System.out.println(s.tag);  //编译不通过
        System.out.println(((Parent)s).tag);
    }
}
class Son extends Parent{
    private String tag="编程思想";
}

最后最后最重要的一点:自己摸索的规律
不管在什么地方,调用方法或者调用构造,这个引用调用的时候,如果延伸到子类或者父类,不管在哪里只要出现this,这个this的运行时类型就是它调用时候的类型。而不管这个this出现在父类或者子类也好,这个this一定是引用调用时候的类型。而编译类型是这个this处在哪个类当中,就指的是哪个类。
暂时到这里

posted on 2012-06-12 00:12 朔望魔刃 阅读(279) 评论(0)  编辑  收藏 所属分类: java

只有注册用户登录后才能发表评论。


网站导航: