您的位置:首页 > 其它

关于类的构造方法的疑问解答

2010-03-29 10:31 309 查看
类可以没有构造方法,但如果有多个构造方法,就应该要有默认的构造方法,否则在继承此类时,需要在子类中显式调用父类的某一个非默认的构造方法了。这句是什么意思啊?谢谢!

这种问题得从java编译器与JVM的角度才能理解透彻

首先java编译器在编译“.java”的源文件时会找出所有与源文件名相同
的“类实例初始化方法”(也就是您所说的“构造方法”),
没有参数的“类实例初始化方法”就是您所说的“默认的构造方法”

/*
ClassA1.java
*/
public class ClassA1 {
ClassA1() {}
ClassA1(int i) {}
ClassA1(String s) {}
}

上面的代码中与源文件名“ClassA1”同名的“类实例初始化方法” 就有三个
1.ClassA1()
2.ClassA1(int i)
3.ClassA1(String s)
其中第1个就是“默认的构造方法”

当用sun公司的javac编译器编译ClassA1.java文件时,
编译器会把所有的“类实例初始化方法” 分别编译成
对应的称为 <init> 的方法
(这里可能不太好理解,说简单点,就是同一种东西有两种叫法一样
“类实例初始化方法”是在“.java”源文件的叫法,而“ <init> ”则
是“.class”文件内部的叫法)

对应上面的例子编译器在生成的.class文件中将包含三个 <init>
1. <init> ()V
2. <init> (I)V
3. <init> (Ljava/lang/String;)V

“ <init> ”就相当于上面的“ClassA1”,()与源文件意思一样,“I”代表“int”
“Ljava/lang/String;”就代表“String”,
“V”就代表“void”,表示方法没有返回值(编译器自动加上的,源文件中是没有的)

另外,如果编译器在编译源文件中没找到“默认的构造方法”
(注:每个源文件中“默认的构造方法”只有一个)
也没找到其他非“默认的构造方法”
像下面的ClassA2.java

//ClassA2.java
public class ClassA2 {
}
编译器会自动在生成的.class文件包含一个“ <init> ()V”

但如果编译器在源文件中找到了其他的非“默认的构造方法”
如下面的ClassA3.java那样

//ClassA3.java
public class ClassA3 {
ClassA3(int i) {}
ClassA3(String s) {}
}
编译器只会生成两个 <init> :
1. <init> (I)V
2. <init> (Ljava/lang/String;)V

而不会像ClassA1.java那样多加一个“ <init> ()V”

最后再回到继承问题:
“类可以没有构造方法,但如果有多个构造方法,就应该要有默认的构造方法,
否则在继承此类时,需要在子类中显式调用父类的某一个非默认的构造方法了。”

“类可以没有构造方法”,这句话是正确的.

“但如果有多个构造方法,就应该要有默认的构造方法”
这句话如果不考虑后面的
“否则在继承此类时,需要在子类中显式调用父类的某一个非默认的构造方法了。”
就不对了,加上才正确。

怎么理解呢?还得说到编译器,还得说到JAVA虚拟机(JVM)

/*
ClassB1.java
*/
public class ClassB1 extends ClassA1 {
ClassB1() {}
}

上面的ClassB1.java就能编译通过

/*
ClassB2.java
*/
public class ClassB2 extends ClassA3 {
ClassB2() {}
}

但上面的ClassB2.java就不能。

为什么呢?
因为按照SUN的JVM规范的要求,当创建一个类实例(也称对象)时,
JVM是通过“类实例初始化方法”来初始实例字段的,如果该类不是Object,
在初始化此类的实例字段之前,得先初始化直接超类的实例字段,一层一层的往上推,
最先被初始的永远是Object的实例字段。

这一点可能理解起来也费劲,要弄清楚是怎么实现对超类实例初始化的,
还得回到 <init>
(注:在 <init> 方法的字节代码中有一条调用直接超类 <init> 的invokespecial字节码指令)

编译器在将每个“类实例初始化方法”编译成对应的 <init> 的过程中,
还会看“类实例初始化方法”的第一条语句是否是以this()调用开始的
/*
ClassB3.java
*/
public class ClassB3 {
ClassB3() {
this(1);
//...其他语句....
}
ClassB3(int i) {
}
}

像上面的那样,JVM机在执行到this(1);时会转到ClassB3(int i)
如果ClassB3(int i)的第一条语句不是this(),且ClassB3(int i)
的第一条语句也不是super之类的调用,那么JVM会默认调用无参数的super(),
无参数的super()就相当于调用直接超类的“默认构造方法”,如果直接超类
没有“默认构造方法”的话,肯定不符合规范的要求。
(注:源文件中的super调用相当于class文件中的invokespecial字节码指令)

但如果直接超类中有其他的非“默认构造方法”,这时就可以在子类相应的“类实例初始化方法”
第一条语句中显示的调用有参数的super(),

public class ClassB2 extends ClassA3 {
ClassB2() {}
}
错在哪里呢?因ClassA3没有“默认构造方法”,
在ClassB2()也没有this,也没有super之类的调用
编译器按JVM规范的要求肯定事先设定了编译流程,
不符合要求的程序代码自然不会编译通过。

改成像下面那样就符合要求了:
public class ClassB2 extends ClassA3 {
ClassB2() {
super(1);
}
}

说了那么多,可能越看越糊涂,还有好多细节没说,
最好用javap命令看看编译器生成后的class文件内容,
对学习可能会有帮助。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: