JAVA类继承过程中其成员的一些问题
构造函数不能继承。子类的构造函数可以通过super关键字显式调用父类中的构造函数。如果子类中的构造函数没有显式调用父类中的构造函数,编译器就会自动在子类的构造函数中调用父类中参数为空的构造函数。于是,当父类中没有参数为空的构造函数,而子类中又没有显示调用父类的其他构造函数,编译时就会报错。这一点需要特别注意。当父类中没有定义任何构造函数时,编译器就会为它指定一个参数为空的默认的构造函数;如果父类中定义了构造函数,那么编译器就不会为它指定一个参数为空的默认构造函数了。因此,如果某个类有可能成为其他类的父类,为了避免发生不必要的编译错误,最好为它编写一个参数为空的构造函数。
eg1.父类Sup中没有定义构造函数,编译程序将为它指定一个参数为空的默认构造函数。子类Sub中也没有定义构造函数,编译程序也会为它指定一个参数为空的默认的构造函数,并且会在这个默认的构造函数中调用父类的参数为空的构造函数。
public class Sub extends Sup{
//子类中没有定义构造函数
public static void main(String args[]){
Sub sub=new Sub();
}
}
class Sup{
//父类中没有定义构造函数
}
eg2.父类Sup中没有定义构造函数,编译程序将为它指定一个参数为空的默认构造函数。子类定义了一个带整型参数的构造函数,在这个构造函数中子类没有显式调用父类的构造函数,所以编译器为在它里面调用父类中参数为空的构造函数。
public class Sub extends Sup{
//子类中定义类一个带整型变量参数的构造函数
public Sub(int i){
//
}
public static void main(String args[]){
Sub sub=new Sub(1);
}
}
class Sup{
//父类中没有定义构造函数
}
eg3.父类中定义了一个带整型参数的构造函数,因此编译器不再为它指定参数为空的默认的构造函数。子类中也定义了一个带整型参数的构造函数。编译时,编译器将试图在子类的构造函数中调用父类的参数为空的构造函数,但是父类中没有定义参数为空的构造函数,所以编译程序将会报错。排错的方法时在子类的构造函数中显示调用父类的构造函数,或者在父类中添加一个带空参数的构造函数。
public class Sub extends Sup{
//子类中定义类一个带整型变量参数的构造函数
public Sub(int i){
//
}
public static void main(String args[]){
Sub sub=new Sub(1);
}
}
class Sup{
//父类中定义了一个带整型参数的构造函数
public Sup(int i){
//
}
}
排错方法1:
public class Sub extends Sup{
//子类中定义类一个带整型变量参数的构造函数
public Sub(int i){
super(i);//调用父类中的构造函数
}
public static void main(String args[]){
Sub sub=new Sub(1);
}
}
class Sup{
//父类中定义了一个带整型参数的构造函数
public Sup(int i){
//
}
}
排错方法2:
public class Sub extends Sup{
//子类中定义类一个带整型变量参数的构造函数
public Sub(int i){
//
}
public static void main(String args[]){
Sub sub=new Sub(1);
}
}
class Sup{
//父类中定义了一个带整型参数的构造函数
public Sup(int i){
//
}
//定义一个带空参数的构造函数
public Sup(){
//
}
}
二、类方法和实例方法在继承过程中的问题:
首相要明确一个概念,JAVA中通过什么来区别不同的成员——答案是标记。所谓标记,对于方法而言是指方法的名字、参数的数量和参数的类型;对于变量而言是指变量的名称。注意,标记不包括方法的返回值和变量的类型。
如果子类的实例方法和父类的实例方法具有相同的标记,子类的方法将覆盖父类的方法。但是如果子类的方法和父类的方法具有相同的标记,但是具有不同的返回值类型,编译程序将会报错。排错的方法是修改子类中这个方法的返回值类型,使它父类中相同标记的方法的返回值一样。
public class Sub extends Sup{
//子类中的这个方法父类中的方法有相同的标记,
//但返回值不同,编译程序就会报错
int test(){
return 1;
}
}
class Sup{
void test(){
//
}
}
排错:
public class Sub extends Sup{
//子类中的方法正确覆盖了父类中的方法
void test(){
//
}
}
class Sup{
void test(){
//
}
}
同样如果子类的类方法和父类的类方法具有相同的标记,那么子类的类方法将隐藏父类的类方法。注意这里不叫覆盖,而叫隐藏,两者的区别将在后面讨论。同样,如果子类的类方法和父类的类方法具有相同的标记和不同的返回值,编译程序也会报错。解决的问题的方法同实例方法一样。
但是实例方法不能覆盖类方法,类方法也不能隐藏实例方法,否则编译程序就会报错。
实例方法不能覆盖类方法(编译时报错):
public class Sub extends Sup{
//实例方法不能覆盖类方法,编译时报错
void test(){
//
}
}
class Sup{
static void test(){
//
}
}
类方法不能隐藏实例方法(编译时报错): public class Sub extends Sup{
//类方法不能隐藏实例方法,编译时报错
static void test(){
//
}
}
class Sup{
void test(){
//
}
}
覆盖和隐藏的区别。对于隐藏的方法,运行时系统调用当前对象引用的编译时类型中定义的方法;对于覆盖的方法,运行时系统调用当前对象引用运行时类型中定义的方法。一个是调用编译时类型中定义的方法,一个是调用运行时类型中定义的方法。通过下面这个例子可以直观的看出两者之间的差别。
class Planet {
public static void hide() {
System.out.println("The hide method in Planet.");
}
public void override() {
System.out.println("The override method in Planet.");
}
}
class Earth extends Planet {
public static void hide() {
System.out.println("The hide method in Earth.");
}
public void override() {
System.out.println("The override method in Earth.");
}
public static void main(String args[]) {
Earth earth = new Earth();
Planet planet = (Planet) earth;
planet.hide(); // result:"The hide method in Planet."
planet.override(); // result:"The override method in Earth."
}
}
Earth类覆盖了Planet类的override方法,并隐藏了它的hide方法。在main方法中创建了一个Earth的实例,并将此实例转换为一个Planet的引用。然后通过Planet引用调用override方法和hide方法。hide方法是类方法,因此根据引用的类来决定调用哪个方法,所以planet.hide()调用的是Planet的类方法hide。override方法是实例方法,因此调用的是运行时的实例中的方法,因为planet引用的是Earth的实例,因此planet.override()调用的是Earth类中实例方法override。
JAVA正是通过实例方法的这种覆盖关系来实现多态的目的。
三、类的变量在继承过程中的问题:
如果子类中的变量和父类中的变量具有相同的标记即相同的名字,那么子类中的变量就会隐藏(类似于类函数)父类中的变量。不管他们的类型是什么,也不管他们是类变量还是实例变量,只要他们具有相同的名字。
public class Sub extends Sup {
float i; //子类中的实例变量隐藏父类中的类变量,并且具有不同的类型
static float j; //子类中的类变量隐藏父类中的实例变量,并且具有不同的类型
int k; //子类中的实例变量隐藏父类中的实例变量
static int l; //子类中的类变量隐藏父类中的类变量
}
class Sup {
static int i ;
int j;
int k;
static int l;
}
同子类中的类方法隐藏父类中的类方法一样,当子类中的变量隐藏父类中的变量的时候,到底使用哪个变量,要根据引用的类型来确定。
下面是一个类成员继承关系的综合例子:
package basic;
public class Subclass extends Superclass {
static String staticString = "static String in Subclass";
String instanceString = "instance String in Subclass";
static void staticPrintf(float a) {
System.out.println("static printf method in Subclass");
}
void instancePrintf(float a) {
System.out.println("instance printf method in Subclass");
}
public static void main(String args[]) {
Subclass sub = new Subclass();
Superclass sup = (Superclass) sub;
System.out.println(sub.staticString); // static String in Subclass
System.out.println(sub.instanceString); // instance String in Subclass
System.out.println(sup.staticString); // static String in Superclass
System.out.println(sup.instanceString); // instance String in Superclass
sub.staticPrintf(1); // static printf method in Subclass
sub.instancePrintf(1); // instance printf method in Subclass
sup.staticPrintf(1); // static printf method in Superclass
sup.instancePrintf(1); // instance printf method in Subclass
}
}
class Superclass {
static String staticString = "static String in Superclass";
String instanceString = "instance String in Superclass";
static void staticPrintf(float a) {
System.out.println("static printf method in Superclass");
}
void instancePrintf(float a) {
System.out.println("instance printf method in Subclass");
}
}