一 类的继承
继承的基本思想时基于某个父类的扩展,制定出一个新的子类,子类可以继承父类原有的属性和方法,也可以增加原来父类所不具备的属性和方法,或者直接重写父类中的某些方法。如下图所示:
在Java中使用extends关键字来标识两个类的继承关系。
class Test{
public Test(){ //构造方法
//SomeSentence
}
protected void doSomething(){ //成员方法
//SomeSentence
}
protected Test doIt(){ //方法返回值类型为Test类型
return new Test();
}
}
class Test2 extends Test{ //继承父类
public Test2(){ //构造方法
super(); //调用父类构造方法
super.doSomething(); //调用父类成员方法
}
public void doSomethingnew(){ //新增方法
//SomeSentence
}
public void doSomething(){ //重写父类方法
//SomeNewSentence
}
protected Test2 doIt(){ //重写父类方法,方法返回值类型为Test2类型
return new Test2();
}
}
上例中,Test2类继承了Test类。在子类中可以连同初始化父类构造方法来完成子类初始化操作,既可以在子类的构造方法中使用super()语句调用父类的构造方法,也可以在子类中使用super关键字调用父类的成员方法等,但是子类没有权限调用父类中被修饰为private的方法。
继承不只是扩展父类的功能,还可以重写父类的成员方法。重写就是在子类中将父类的成员方法的名称保留,重写成员方法的实现内容,更改成员的方法的存储权限,或是修改成员方法的返回值类型。
在继承中还有一种特殊的重写方式,子类与父类的成员方法返回值、方法名称、参数类型及个数完全相同,唯一不同的是方法实现内容,这种特殊重写方式被称为重构。
当重写父类方法时,修改方法的修饰权限只能从小的范围到大的范围改变。
子类重写父类的方法还可以修改方法的返回值类型,如例子中子类的doIt()方法。这种重写方式需要遵循一个原则,即重写的返回值类型必须是父类中同一方法返回值类型的子类,而Test2类正是Test类的子类。
在Java中一切都以对象的形式进行处理,在继承的机制中,创建一个子类对象,将包含一个父类子对象,这个对象与父类创建的对象是一样的。两者的区别在于后者来自外部,前者来自子类对象的内部。当实例化子类对象时,父类对象也相应被实例化,换句话说,在实例化子类对象时,Java编译器会在子类的构造方法中自动调用父类的无参构造方法。如下例所示:
class Test1{
Test1(){
System.out.println("调用父类的Test1()构造方法");
}
}
class Test2 extends Test1{ //继承Test1类
Test2(){
System.out.println("调用子类的Test2()构造方法");
}
}
public class Test3 extends Test2{ //继承Test2类
Test3(){
System.out.println("调用子类的Test3()构造方法");
}
public static void main(String[] args){
Test3 t = new Test3(); //实例化子类对象
}
}
运行结果如下:
从上例的运行结果可以看出,在子类Test3的主方法中之调用子类的构造方法实例化子类对象,并且在子类构造方法中没有调用父类构造方法的任何语句,但是在实例化子类对象时它相应调用了父类的构造方法。
在实例化子类对象时,父类无参构造方法将被自动调用,但有参构造方法并不能被自动调用,只能依赖与super关键字显式地调用父类的构造方法。
如果使用finalize()方法对对象进行清理,需要确保子类的finalize()方法的最后一个动作是调用父类的finalize()方法,以保证当垃圾回收对象占用内存时,对象的所有部分都能被正常终止。
二 Object类
在Java中,所有的类都直接或间接继承了java.lang.Object类。Object类是所有类的父类,是Java类层中的最高层类。在Object类中主要包括clone(), finalize(), equals(), toString()等方法,其中常用的两个方法为equals()和toString()方法。由于所有的类都是Object类的子类,所以任何类都可以重写Object类中的方法。
Object类中的getClass(),notify(),notifyAll,wait()等方法不能被重写,因为这些方法被定义为final类型。
Object类中的3个重要方法:
1. getClass()方法
getClass()方法会返回对象执行时的Class实例,然后使用此实例调用getName()方法可以取得类的名称。可以将getClass()方法与toString()方法联合使用。语法如下:
getClass().getName();
2. toString()方法
toString()方法的功能是将一个对象返回为字符串形式,它会返回一个String实例。在实际的应用中通常重写toString()方法,为对象提供一个特定的输出模式。当这个类转换为字符串或与字符串连接时,将自动调用重写的toString()方法。
3. equals()方法
equals()方法用来比较两个对象的实际内容,而\“==\”比较的是两个对象的引用是否相等。
三 对象类型的转换
对象类型的转换在Java编程中经常遇到,主要包括向上转型与向下转型操作。
3.1 向上转型
class Quadrangle{ //四边形类
public static void draw(Quadrangle q){
//somesentence
}
}
public class Parallelogram extends Quadrangle{ //平行四边形类,继承了四边形类
public static void main(String args[]){
Parallelogram p = new Parallelogram();
draw(p); //调用父类方法
}
由于向上转型是从一个较具体的类到较抽象的类的转换,所以它总是安全的,如可以说平行四边形是特殊的四边形,但不能说四边形是平行四边形。
3.2 向下转型
向下转型是将较抽象类转换为较具体的类。这样的转型通常会出现问题。
class Quadrangle{
public static void draw(Quadrangle q){
//SomeSentence
}
public class Parallelogram extends Quadrangle{
public static void main(String args[]){
draw(new Parallelogram());
Quadrangle q = new Parallelogram();
//Parallelogram p = q; 将父类对象赋予子类对象,这种方法是错误的;
Parallelogram p = (Parallelogram)q; // 将父类对象赋予子类对象,并强制转换类型,这种方法是正确的
}
}
}
越是具体的对象具有的特性越多,越抽象的对象具有的特性越少,在做向下转型操作时,将特性范围小的对象转换为特性范围大的对象肯定会出现问题,所以这时需要告知编译器这个四边形就是平行四边形。当在程序中使用向下转型技术是,必须使用先使类型转换,向编译器指明将父类对象转换为哪一种类型的子类对象。
四 使用instanceof操作符判断对象类型
当在程序中执行向下转型操作时,如果父类对象不是子类对象的实例,就会发生ClassCastException异常,所以在执行向下转型之前需要养成好习惯,判断父类对象是否为子类对象的实例。这个判断通常使用instanceof操作符来完成。可以使用instanceof操作符判断是否一个类实现了某个接口,也可以用它来判断一个实例对象是否属于一个类。语法格式如下:
myobject instanceof ExampleClass
myobject为某类的对象引用,ExampleClass为某个类。使用instanceof操作符的表达式返回值为布尔值。如果返回值为true,说明myobject对象为ExampleClass的实例对象;如果返回值为false,说明myobject对象不是ExampleClass的实例对象。
class Quadrangle{
public static void draw(Quadrangle q){
//somesentence
}
}
class Square extends Quadrangle{
//somesentence
}
class Anything{
//somesentence
}
public class Parallelogram extends Quadrangle{
public static void main(String args[]){
Quadrangle q = new Quadrangle(); //实例化父类对象
//判断父类对象是否为Parallelogram子类的一个实例
if(q instanceof Parallelogram){
Parallelogram p = (Parallelogram) q; //向下转型操作
}
//判断父类对象是否为Square子类的一个实例
if(q instanceof Square){
Square s = (Square) q; //进行向下转型操作
}
//System.out.println(q instanceof Anything); 由于q对象不为Anything类的对象,所有这条语句是错误的
}
}
五 方法的重载
方法的重载就是在同一个类中允许同时存在一个以上的同名方法,只要这些方法的参数个数或类型不同即可。
public class OverLoadTest{
public static int add(int a, int b){
return a + b;
}
public static double add(double a, double b){
return a + b;
}
public static int add(int a){
return a;
}
public static int add(int a, double b){
return 1;
}
public static int add(double a, int b){
return 1;
}
public static void main(String args[]){
System.out.println("调用add(int, int)方法:" + add(1, 2));
System.out.println("调用add(double, double)方法:" + add(1.2, 2.3));
System.out.println("调用add(intt)方法:" + add(1));
}
}
运行结果如下:
根据上文可知,构成方法重载的条件是利用方法名、方法各参数类型和参数的个数以及参数的顺序来确定类中的方法是否唯一。注意:虽然在方法重载中可以使两个方法的返回值不同,但只有返回值不同并不足以区分两个方法的重载。
六 多态
利用多态可以使程序具有良好的扩展性,并可以对所有类对象进行通用的处理。对象可以作为父类的对象实例使用,这种将子类对象视为父类对象的做法称为“向上转型”。假如现在需要绘制一个平行四边形,可以在平行四边形类中定义一个draw()方法,代码如下:
public class Parallelogram{
public void draw(Parallelogram p){
...//绘图语句
}
}
如果定义一个四边形类,让它处理所有继承该类的对象,根据“向上转型”原则可以使每个继承四边形类的对象作为draw()方法的参数,然后在draw()方法中作一些限定就可以根据不同图形类对象绘制相应的图形,从而以更为通用的四边形类来取代具体的正方形类和平行四边形类。如下例:
public class Quadrangle{
//实例化保存四边形对象的数组对象
private Quadrangle[] qtest = new Quadrangle[6];
private int nextIndex = 0;
public void draw(Quadrangle q){ //定义draw()方法,参数为四边形对象
if (nextIndex < qtest.length){
qtest[nextIndex] = q;
System.out.println(nextIndex);
nextIndex++;
}
}
public static void main(String[] args){
//实例化两个四边形对象,用于调用draw()方法
Quadrangle q = new Quadrangle();
q.draw(new Square()); //以正方形对象为参数调用draw()方法
q.draw(new Parallelogramgle()); //以平行四边形对象为参数调用draw()方法
}
}
//定义一个正方形类,继承四边形类
class Square extends Quadrangle{
public Square(){
System.out.println("正方形");
}
}
//定义一个平行四边形类,继承四边形类
class Parallelogramgle extends Quadrangle{
public Parallelogramgle(){
System.out.println("平行四边形");
}
}
执行结果如下:
七 抽象类与接口
7.1 抽象类
在解决实际问题时,一般将父类定义为抽象类,需要使用这个父类进行继承与多态处理。在Java语言中设置抽象类不可以实例化对象,因为图形类不能抽象出任何一种具体的图形,但它的子类却可以。
抽象类的语法如下:
public abstract class Test{
abstract void testAbstract(); //定义抽象方法
}
其中abstract是定义抽象类的关键字。
如果声明一个抽象方法,就必须将承载这个抽象方法的类定义为抽象类。换句话说,只要类中又一个抽象方法,此类就被标记为抽象类。
抽象类被继承后需要实现其中所有的抽象方法,也就是保证相同方法名称、参数列表和相同返回值类型创建出非抽象方法,当然也可以是抽象方法。
抽象类的继承关系,如下图所示:
从上图可以看出,继承抽象类的所有子类需要将抽象类中的抽象方法进行覆盖。在多态机制中,将父类修改为抽象类,将draw()方法设置为抽象方法,然后每个子类重写这个方法。
但这样会产生一个问题:如果某个子类不需要draw()方法,也同样不得不重写draw()方法。而Java中又不允许类同时继承多个父类,这种情况就需要使用接口进行处理。
7.2 接口
7.2.1 接口简介
接口是抽象类的延伸,可以将它看作是纯粹的抽象类,接口中的所有方法都没有方法体。对于7.1中遗留的问题,可以将draw()方法封装到一个接口中,使需要draw()方法的类实现这个接口,同时也继承图形类,这就是接口存在的必要性。
接口使用interface关键字进行定义,其语法如下:
public Interface drawTest{
void draw(); //接口内的方法,省略abstract关键字
}
一个类实现一个接口可以使用implements关键字,代码如下:
public class Parallelogram extends Quadrangle implements drawTest{
...//
}
在接口中定义的方法必须被定义为public或abstract形式。在接口中定义的任何字段都自动是static和final的。
例:在项目中创建QuadrangleUseInterface类,在类中创建两个继承该类的内部类ParallelogramgleUseInterface和SquareUseInterface;再创建drawTest接口,并使前两个内部类实现该接口;然后再主方法中分别调用这两个内部类的draw()方法。
package notebook;
interface drawTest{ //定义接口
public void draw(); //定义方法
}
//定义平行四边形类,该类继承了四边形类,并实现了drawTest接口
class ParallelogramgleUseInterface extends QuadrangleUseInterface implements drawTest{
public void draw(){ //由于该类实现了接口,所以要覆盖draw()方法
System.out.println("平行四边形.draw()");
}
void doAnyThing(){ //覆盖父类方法
//SomeSentence
}
}
class SquareUseInterface extends QuadrangleUseInterface implements drawTest{
public void draw(){ //由于该类实现了接口,所以要覆盖draw()方法
System.out.println("正方形.draw()");
}
void doAnyThing(){ //覆盖父类方法
//SomeSentence
}
}
class AnyThingUseInterface extends QuadrangleUseInterface{
void doAnyThing(){ //覆盖父类方法
//SomeSentence
}
}
public class QuadrangleUseInterface{
public void doAnyThing(){
//SomeSentence
}
public static void main(String[] args){
drawTest[] d = {new SquareUseInterface(), new ParallelogramgleUseInterface()}
for(int i = 0; i < d.length, i++){
d[i].draw();
}
}
}
运行结果如下:
7.2.2 接口与继承
Java中不允许多重继承,但使用接口可以实现多次继承,因为一个类可以同时实现多个接口。但这可能会在一个类中产生庞大的代码量,因为继承一个接口时需要实现接口中所有的方法。
多次继承的语法如下:
class 类名 implements 接口1,接口2, ... ,接口n
另外,在定义一个接口时,可以使该接口继承另一个接口:
Interface intf1{
}
Interface Intf2 extends intf1{
}