最近一直在看设计模式和数据结构,把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
朔望魔刃 阅读(287)
评论(0) 编辑 收藏 所属分类:
java