继承是面向对象设计中一个重要的设计方法,Java对它有着很好的支持。
1、简介
通过继承,可以利用一个现有的类生成另外一个新类,同时新类自动获得原有类的成员,并且新类可以进行修改和增添新成员。
如在一个矢量绘图软件中,需要不同的类来表达不同的几何图形,其中的很多类之间存在依存关系,如圆(Circle)中的圆心就是一个点(Point),代码如下:
点类:
public class Point
{
private int x;
private int y;
public void setX(int i)
{
x=i;
}
public int getX()
{
return x;
}
public void setY(int i)
{
y=i;
}
public int getY()
{
return y;
}
public String toString()
{
return "("+x+","+y+")";
}
}
一个最为简单的圆类:
public class Circle extends Point
{
}
应用程序为:
public class exec
{
public static void main(String args[])
{
Circle c=new Circle();
c.setX(1);
c.setY(2);
System.out.println("X="+c.getX());
System.out.println("Y="+c.getY());
}
}
输出为:
X=1
Y=2
说明:
1)一个类继承另外一个类的方式是使用extends语句,如A extends B,表示A类继承B类,B类称为父类或者基类,A类称为子类或者派生类。
2)子类默认自动获得父类的全部成员,包括属性和方法,所以上述应用程序中圆类具有了操作点类的全部方法,显然具有了表达圆心所需要的功能。
2、子类的变异
当然,子类如果和父类一模一样是没有意义的,所以,我们可以通过两种方式来修改子类的成员。
2、1 子类添加新成员
如圆类应该还有表达半径的属性成员和相应的操作方法,此时只需直接在子类中增添新的成员,如:
public class Circle extends Point
{
private int r;
public void setR(int rr)
{
r=rr;
}
public int getR()
{
return r;
}
}
应用程序为:
public class exec
{
public static void main(String args[])
{
Circle c=new Circle();
c.setX(1);
c.setY(2);
c.setR(5);
System.out.println("X="+c.getX());
System.out.println("Y="+c.getY());
System.out.println("R="+c.getR());
}
}
输出为:
X=1
Y=2
R=5
甚至可以对代表圆心的点类中的成员提供更高层次上的操作方法,如:
public class Circle extends Point
{
private int r;
public void setR(int rr)
{
r=rr;
}
public int getR()
{
return r;
}
public void setPoint(int i,int j)
{
setX(i);
setY(j);
}
public String getPoint()
{
return toString();
}
}
应用程序为:
public class exec
{
public static void main(String args[])
{
Circle c=new Circle();
c.setPoint(1,2);
c.setR(5);
System.out.println("(X,Y)="+c.getPoint());
System.out.println("R="+c.getR());
}
}
输出为:
(X,Y)=(1,2)
R=5
这种继承可以层层嵌套,产生多重继承结构,如对于圆柱型,则可以通过继承圆类来表达圆柱型的底面。
2、2 子类修改继承的父类成员
如点类的toString()方法被圆类继承以后,仍然输出点的信息,此时对于圆类而言,toString()将显得不是很正确,应该是输出圆的全部信息。
可以通过在子类中重新定义具有相同函数原型的方法,来修改子类继承过来的父类成员,如:
圆类:
public class Circle extends Point
{
private int r;
public void setR(int rr)
{
r=rr;
}
public int getR()
{
return r;
}
public void setPoint(int i,int j)
{
setX(i);
setY(j);
}
public String getPoint()
{
return super.toString();
}
public String toString()
{
return "("+getX()+","+getY()+")"+"--"+r;
}
}
应用程序为:
public class exec
{
public static void main(String args[])
{
Circle c=new Circle();
c.setPoint(1,2);
c.setR(5);
System.out.println(c);
}
}
输出为:
(1,2)--5
注意:
1)如果子类和父类具有一个同名方法,外界在调用子类对象的这个方法时,总是使用子类的方法。此时,父类的同名方法被子类隐藏起来了。这种机制称之为“重写”(Override)。
2)重写的方式会导致父类的同名方法不会在子类同名方法被调用时被执行,所以,一般需要在子类方法中将父类同名方法中的必要功能代码重新书写一次。如上述程序中,可以看出Circle的toString()方法和Point类的toString()方法存在相同的代码部分,都需要构造点的表达方式,如"("和")"等。利用系统提供的super引用可以在子类中访问到被隐藏起来的父类同名方法,利用这种方式可以有效的复用被隐藏起来的父类同名方法,如Circle类中新的toString()方法:
public String toString()
{
return super.toString()+"--"+r;
}
3、protected类型
添加访问修饰符protected修饰父类的成员,可以只允许子类直接访问父类中的该成员,而其他的一般类都不行,这样使得父类可以将直接访问权限只赋予子类,如:
点类:
public class Point
{
protected int x;
protected int y;
public void setX(int i)
{
x=i;
}
public int getX()
{
return x;
}
public void setY(int i)
{
y=i;
}
public int getY()
{
return y;
}
public String toString()
{
return "("+x+","+y+")";
}
}
圆类:
public class Circle extends Point
{
private int r;
public void setR(int rr)
{
r=rr;
}
public int getR()
{
return r;
}
public void setPoint(int i,int j)
{
x=i;
y=j;
}
public String getPoint()
{
return super.toString();
}
public String toString()
{
return super.toString()+"--"+r;
}
}
程序功能和刚才的一样,但是由于子类可以直接访问父类的成员,所以访问性能更好,代码显得更加简洁。
注意:Java语言中有个额外的规定,那就是protected类型的成员也可以看成是friendly类型,因此,同一个包里的其他类也可以直接访问该类的protected类型成员。
4 继承中的构造函数
观察下面程序的运行情况:
一个类:
public class A
{
public A()
{
System.out.println("A");
}
}
另外一个继承上述类的类:
public class B extends A
{
public B()
{
System.out.println("B");
}
}
应用程序为:
public class exec
{
public static void main(String args[])
{
B b=new B();
}
}
输出为:
A
B
说明:子类在调用构造函数进行对象创建的时候,将首先调用父类的构造函数,原因在于父类的构造函数中可能存在初始化父类成员的代码,如果此时直接执行子类的构造函数,就会导致子类可能因为使用到那些没有被正确初始化的父类成员而产生错误。
但是有时会出现问题,如:
public class A
{
public A(String str)
{
System.out.println(str);
}
}
public class B extends A
{
public B()
{
System.out.println("B");
}
}
此时在编译B类时会产生错误:cannot resolve symbol
symbol : constructor A ()
location: class A
原因在于子类构造函数在调用父类构造函数的时候,无法正确的将父类构造函数所需要的参数传递过去。
此时有两种解决方法,一为在父类A中添加默认构造函数(即无参构造函数),如:
public A()
{}
二为子类将参数传递给父类,如:
public class B extends A
{
public B()
{
super("A");
System.out.println("B");
}
}
说明:这是super关键字的第二种用法,表示父类的构造函数。其实子类构造函数之所以会在执行自己的构造函数之前调用父类的构造函数,就是因为子类构造函数的第一行默认总是super()这一句。
5、Object类
它提供了一些所有类都需要的基本方法成员,它也是所有类的基类,任何类都是派生于此类,即如果一个类不显式的表明基类,则默认就是派生于此类,如:
public class A
{
public A(String str)
{
System.out.println(str);
}
}
等价于
public class A extends Object
{
public A(String str)
{
System.out.println(str);
}
}