关于类的构造方法的疑问解答
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文件内容,
对学习可能会有帮助。
这种问题得从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文件内容,
对学习可能会有帮助。
相关文章推荐
- 关于static语句,构造代码块,构造方法的执行顺序
- 关于QT 中 QDialog的几点疑问的解答
- ruby 疑问,关于hash方法的@x*31 ^ @y*13 ^ @z
- 关于子类调用父类构造方法的研究
- 关于Storm的一些疑问解答
- 关于子类为何不能继承父类构造方法的几点解释
- 关于构造方法
- 解答客户一个关于clob存储的疑问
- 关于Java中的默认构造方法
- 关于java代码中静态代码块、非静态构造代码块以及构造方法的执行顺序
- 关于a标签中js函数function(va1,va2)方法传递中文参数报错不执行的问题解答
- 关于static语句,构造代码块,构造方法的执行顺序
- 关于构造方法特性的总结
- 关于方法和构造方法的一些问答
- 关于hibernate悲观锁设置方法setLockMode无效而setLockOptions有效的解答
- (Redundancy)关于服务器冗余的几个疑问,请知道的帮忙解答.
- 解答客户一个关于clob存储的疑问
- 关于论坛上那个SQL微软面试题。我的解答方法 :-)
- 负数补码和负数本身进行转换的一个好方法---解答一网友的疑问
- C++关于子类调用父类的构造方法的问题