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

来源:互联网 发布:编写软件怎么自学 编辑:程序博客网 时间:2024/06/13 23:47

------- 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);//4System.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培训 期待与您交流! -------


0 0