您的位置:首页 > 编程语言 > Java开发

04_java基础_继承、抽象、多态

2015-05-05 08:30 411 查看
------- android培训java培训.net培训IOS培训
期待与您交流! -------

继承

继承的初衷是为了提高代码的复用,我们可以把不同对象所具有的共同属性提取出来封装到一个类中,作为其他类的父类。当某个类需要使用这些共性方法的时候,只需要从父类继承过来就可以了,不用再在新类中去书写。A继承B,B是父类,A是子类。继承的关键字是extends。

什么时候定义继承?

必须保证类与类之间有所属(is a)关系。

比如,苹果是水果中一种。狗是犬科中一种。

在Java中继承的体现:

Java允许单继承。不直接支持多继承,将多继承进行其他方式的体现。

单继承:一个子类只能有一个父类。

多继承:一个子类可以有多个父类。

继承如何使用?

多继承:
<pre name="code" class="java">class Fu1
{
void show1()
{}
}

class Fu2
{
void show2()
{}
}

//多继承。
class Zi extends Fu1,Fu2
{

}

Zi z = new Zi();//子类继承了父类,子类就有了父类的方法。
z.show1();
z.show2();




多重继承:
class A
{}
class B extends A
{}
class C extends B
{}
多重继承,形成了继承体系。

学习继承体系时,应该首先参阅顶层类中的内容。

了解这个体系的基本功能。

使用这个体系功能,需要创建最子类的对象。

看顶层,建底层。

Super和this:

子父类中定义了一模一样的成员变量。

都存在于子类对象中。

如何在子类中直接访问同名的父类中的变量呢?

通过关键字 super来完成。

super和this的用法很相似。

this:代表的是本类的对象的引用。

super:代表的是父类的内存空间。

但是,父类中私有的内容子类不可以直接访问。

class Fu
{
int num = 3;
}

class Zi extends Fu
{
int num = 4;
void show()
{
System.out.println("zi num="+this.num);//4
System.out.println("fu num="+super.num);//3

}
}

class ExtendsDemo
{
public static void main(String[] args)
{
Zi z = new Zi();
z.show();
}
}


如果子类中定义了和父类一样的函数,那么运行时,运行的是子类的函数。这种事继承关系中的另一个特性:覆盖或者叫重写或者叫复写。

复写什么情况下使用?

当父类的函数功能已经不能满足子类需要的时候,可以复写父类的函数。也就是对父类的函数进行升级。

复写的注意事项:

当复写父类方法时,子类实现的方法权限必须大于等于父类该方法的权限。
写法上必须要一模一样,比如函数的返回类型,函数名,参数列表都要一样,才能达到复写的效果。

子父类中构造函数的特点:

如果父类中有空参的构造函数,那么子类中所有的构造函数中,第一行都有一个隐式的super()。原因是,子类继承父类是要使用父类中的内容的,而只有将父类初始化才能更方便的使用父类中的内容。

如果父类中没有空参的构造函数,那么子类的构造函数中必须显式地使用super(加参数)来指定使用父类的哪个构造函数来初始化父类。

如果子类构造函数中第一行用this调用了本类的一个构造函数,那么原来隐式的super()就没有了,原因是this()或者super()只能定义在构造函数的第一行。

父类构造函数的第一行是不是也是隐式的super?
是的,只要是构造函数,第一行默认的都是super()。Java中Object是所有的类的父类。

抽象

抽象类:在描述事物时,没有足够的信息描述一个事物,这时该事物就是抽象事物。

描述狗,行为:吼叫。

描述狼,行为:吼叫。

发现他们之间有共性,可以进行向上抽取。

当然是抽取它们的所属共性类型:犬科。

犬科这类事物:都具备吼叫行为,但是具体怎么叫,是不确定的,是由具体的子类来明确的。

这时在描述犬科时,发现了有些功能不具体,这些不具体的功能,需要在类中标识出来,通过java中的关键字abstract(抽象)。

定义了抽象函数的类也必须被abstract关键字修饰,被abstract关键字修饰的类是抽象类。

抽象类和抽象方法都要用abstract修饰。

抽象类不能创建实例,因为抽象类没有实际意义。

子类只有在复写了父类的抽象方法后,子类才可以实例化对象,否则子类还是抽象类。

抽象类一定是父类,因为抽象类某些类的共性组成的,而这些共性来自于子类。

抽象类也有构造函数,但是不能给自己初始化,只能给自己的子类初始化。

抽象类和普通类之间的比较:
相同之处:
都是用来描述事物的。
都可以定义属性和方法。
不同之处:
一般类描述的事物是具体的,抽象类描述的事物是不具体的。
抽象类可以多定义一种成员:抽象函数。
一般类可以实例化对象,抽象类不能实例化对象。

抽象类也可以不定义抽象方法,而此时将该类定义为抽象类的目的是为了不让该类初始化。

不能和abstract共同存在的关键字:final,private,static。

接口

抽象类中可以定义抽象方法,当一个抽象类中的方法全是抽象的时候,这个抽象类就可以用接口来体现。

定义接口的关键字:interface。
实现接口的关键字:implements

接口中的变量修饰符固定:public static final
接口中的方法修饰符固定:public abstract

接口的特点:
接口和抽象类一样,不能创建对象。因为接口和抽象方法一样,没有实际意义。
子类必须复写接口中所有的方法之后,该子类才可以实例化。不然子类就是一个抽象类。

定义一个接口:
interface Demo//定义一个名称为Demo的接口。
{
public static final int NUM = 3;
public abstract void show1();
public abstract void show2();
}


实现接口的类必须复写掉接口中所有的方法:
class DemoImpl implements Demo//子类实现Demo接口。
{
//重写接口中的方法。
public void show1(){}
public void show2(){}
}


接口重要性的体现:
接口的出现可以用多实现代替多继承,解决多继承的弊端。
其实核心原因就是在于多继承父类中方法有主体,而导致调用运行时,不确定运行哪个主体内容。

为什么多实现就解决了呢?

因为接口中的功能都没有方法体,由子类来明确。

多继承的弊端:
当一个类继承了多个父类之后,如果多个父类中定义了名字相同的实例变量,那么子类在调用该实例变量的时候将产生歧义,无法确定要使用哪一个父类的变量。
当一个类继承了多个父类之后,如果多个父类中定义了相同的方法,而子类中有没有对其进行复写,那么在子类调用该方法的时候同样会产生歧义。

多实现演示:
interface A
{
void show1();
}
interface B
{
void show2();
}
class C implements A,B// 多实现。同时实现多个接口。
{
public void show1(){}
public void show2(){}
}


写到这里,我想到如果某个子类实现了两个接口,而两个接口中又定义了相同的变量,那么多实现是不是也有多继承的弊端呢?
于是我写了如下测试代码:
interface A {

public static final int a = 1;

}

interface B {

//	public static final int a = 1;

}

class C implements A,B{

public C(){

System.out.println(a);

}

}


如果上面代码中接口B中哪一行代码不注释掉的话,Eclipse是报错的。还有要注意的就是,抽象类中定义变量是不一定要用到static final关键字的,而接口中只要定义变量,必然使用这两个修饰符。

定义一个类的时候,既可以继承,又可以实现。
class Fu
{
public void show(){}
}
interface Inter
{
pulbic void show1();
}
class Zi extends Fu implements Inter
{
public void show1()
{

}
}


子类通过继承父类来获取子类的基本功能,如果子类还需要扩展其他的功能,就可以通过接口来实现。

类与类之间是继承(is a)关系,类与接口之间是实现(like a)关系,接口与接口之间是继承关系,而且可以多继承。

interface InterA
{
void show1();
}
interface InterAA
{
void show11();
}
interface InterB extends InterA,InterAA//接口的多继承。
{
void show2();
}

class Test implements InterB
{
public void show1(){}
public void show2(){}
public void show11(){}
}

class InterfaceDemo
{
public static void main(String[] args)
{
DemoImpl d = new DemoImpl();
d.show1();
d.show2();

}
}


接口的实现问题:
interface Inter
{
//定义四种显示功能。
public void show1();
public void show2();
public void show3();
public void show4();
}

//定义子类,要使用第一种显示方式。
class InterImpl1 implements Inter
{
//覆盖show1方法。
public void show1()
{
System.out.println("show1 run");
}
//为了让该类实例化。还需要覆盖其他三个方法,虽然该类用不上。
public void show2(){}
public void show3(){}
public void show4(){}
}

//另一个子类需要使用显示3方法。
class InterImpl3 implements Inter
{
//覆盖show3方法。
public void show3()
{
System.out.println("show3 run");
}
//为了让该类实例化。还需要覆盖其他三个方法,虽然该类用不上。
public void show2(){}
public void show1(){}
public void show4(){}
}


但是:
为了使用接口中的部分方法。而覆盖了全部的方法,而且每一个子类都要这么做,复用性差。

将这些不用的方法都单独抽取到一个独立的类中。

让这个类去实现接口,并覆盖接口中的所有方法。

这个类知道这些方法的具体实现内容吗?不知道。

所以只能为了后期子类创建对象方便,而进行空实现。

而这时,这个类创建对象有意义吗?没有意义。这个类创建对象就不需要,直接将其抽象化。

这就是没有抽象方法的抽象类。

这样就可以在使用其中某一个方法的时候不实现其他无关的方法。

效果如下:
abstract class InterImpl implements Inter
{
//实现Inter接口中的所有方法。
public void show1(){}
public void show2(){}
public void show3(){}
public void show4(){}
}
//如果有子类去使用显示1方法。让子类继承InterImpl实现类就可以了。
class InterImpl11 extends InterImpl
{
public void show1()
{
System.out.println("show1 run");
}
}

class InterImpl33 extends InterImpl
{
public void show3()
{
System.out.println("show3 run");
}
}


多态

多态的体现:
父类的引用或者接口的引用指向了自己的子类对象。

Dog d = new Dog();//Dog对象的类型是Dog类型。

Animal a = new Dog();//Dog对象的类型右边是Dog类型,左边Animal类型。

多态是如何提高程序的可扩展性的:

这样的好处是,程序的已知流程在没有派生类的时候就可以写好,以后要有新的功能,只要再写个派生类就可以了。举个例子,在电脑上显示图片,基本上要有读文件,解析文件,显示图形三个步骤,这些步骤可以写在基类中,然后具体的如何读文件,如何解析文件,如何显示,就交给派生类去做。

多态的弊端:
通过父类引用操作子类对象时,只能使用父类中已有的方法,不能操作子类特有的方法。

多态的前提:
1,必须有关系:继承,实现。

2,通常都有重写操作。

那么使用多态创建的对象如何调用子类的方法:
对该对象进行向下转型

Animal a = new Dog();//这一步骤也叫向上转型。
这里Animal是Dog的父类。

向下转型方法:
强制类型转换
Dog d = (Dog)a

但是向下转型是要注意,类型转换是否正确,比如上面的a对象,如果对其进行向下转型应该转成Dog类型,如果进行如下转换就会出ClassCastException异常。

所以为了避免这个问题,需要在向下转型前,做类型的判断。

判断类型用的是关键字 instanceof

if(a instanceof Cat)//a指向的对象的类型是Cat类型。
{
//将a转型Cat类型。
Cat c = (Cat)a;
}
else if(a instanceof Dog)
{
Dog d = (Dog)a;
}


多态中,成员调用的特点。

1,成员变量。

当子父类中出现同名的成员变量时。

多态调用该变量时:

编译时期:参考的是引用型变量所属的类中是否有被调用的成员变量。没有,编译失败。

运行时期:也是调用引用型变量所属的类中的成员变量。

简单记:编译和运行都参考等号的左边。

编译运行看左边。

2,成员函数。

编译,参考左边,如果没有,编译失败。

运行,参考右边的对象所属的类。

编译看左边,运行看右边。

对于成员函数是动态绑定到对象上。

3,静态函数。

编译和运行都参考左边。

静态函数是静态的绑定到类上。

(这些都是可以理解的,没必要死记硬背)

------- android培训java培训.net培训IOS培训 期待与您交流!
-------
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: