首页 > 代码库 > 第七节 继承、抽象类和接口
第七节 继承、抽象类和接口
继承的概述
代码示例:
class Person{ // 父类
String name;
int age;
char sex;
String idcard;
}
class Students extends Person{ //子类 继承 父类的属性
public void study(){
System.out.println("好好学习");
}
}
class Worker extends Person{
//子类 继承 父类的属性
/* 我们通过继承的方式,可以省略掉Worker的属性
*/
public void work(){
System.out.println("我有一份好工作");
}
}
继承:
1、提高了代码的复用性。
2、让类与类之间产生了关系。有了这个关系才有了多态的特性。
注意:
不要为了获取其他类的功能而继承,必须是类与类之间所属关系才可以继承。 所属关系:谁包含谁
java语言中只支持单继承,不支持多继承。因为多继承容易产生隐患:当父类中定义了相同功能,功能实现不同,我们在调用的时候不清楚调用的是哪个功能。多继承经过改良了之后被称之为多实现
但是java支持多层继承。也就是一个继承体系。
如何使用一个继承体系中的功能?
想要使用体系,先查阅体系父类的描述,因为父类中定义的是该体系中共性的内容,因为了解共性功能,就可以了解该功能的基本功能。那么这个体系已经可以基本使用了,
在具体调用时,要创建最子类的对象,因为有可能父类不能创建对象;创建子类对象可以使用更多的功能,包括基本的也包括特有的。--->查阅父类功能,创建子类对象使用功能
子父类中变量的特点
子父类出现后,类成员的特点:
类中成员:
1、变量
如果子类中出线非私有的同名成员变量,用this;
子类要访问父类中的同名变量,用super;
super的使用和this的使用几乎一致,this代表本类对象的引用,super代表父类对象的引用。
2、函数
3、构造函数
继承的特点
函数重载和覆盖
重载(overload)
对于类的方法(包括从父类中继承的方法),方法名相同,参数列表不同的方法之间就构成了重载关系。这里有两个问题需要注意:
(1)什么叫参数列表?参数列表又叫参数签名,指三样东西:参数的类型,参数的个数,参数的顺序。这三者只要有一个不同就叫做参数列表不同。
(2)重载关系只能发生在同一个类中吗?非也。这时候你要深刻理解继承,要知道一个子类所拥有的成员除了自己显式写出来的以外,还有父类遗传下来的。所以子类中的某个方法和父类中继承下来的方法也可以发生重载的关系。
大家在使用的时候要紧扣定义,看方法之间是否是重载关系,不用管方法的修饰符和返回类型以及抛出的异常,只看方法名和参数列表。而且要记住,构造器也可以重载。
覆盖 (override)
也叫重写,就是在当父类中的某些方法不能满足要求时,子类中改写父类的方法。
当父类中的方法被覆盖了后,除非用super关键字,否则就无法再调用父类中的方法了。
发生覆盖的条件:
1、“三同一不低” 子类和父类的方法名称,参数列表,返回类型必须完全相同,而且子类方法的访问修饰符的权限不能比父类低。
2、子类方法不能抛出比父类方法更多的异常。即子类方法所抛出的异常必须和父类方法所抛出的异常一致,或者是其子类,或者什么也不抛出;
3、被覆盖的方法不能是final类型的。因为final修饰的方法是无法覆盖的。
4、被覆盖的方法不能为private。否则在其子类中只是新定义了一个方法,并没有对其进行覆盖。
5、被覆盖的方法不能为static。所以如果父类中的方法为静态的,而子类中的方法不是静态的,但是两个方法除了这一点外其他都满足覆盖条件,那么会发生编译错误。反之亦然。即使父类和子类中的方法都是静态的,并且满足覆盖条件,但是仍然不会发生覆盖,因为静态方法是在编译的时候把静态方法和类的引用类型进行匹配。
java修饰符
子类的实例化过程
子父类中的构造函数
在对子类对象进行初始化时,父类的构造函数也会运行,因为子类的构造函数第一行默认有一条隐士语句super();super()会访问父类中的空的构造函数,而且子类中所有的构造函数默认第一行都是super().
代码示例:
class Fu{
Fu(){
System.out.println("Fu无参")
}
FU(int age){
System.out.println("Fu有参")
}
}
class Zi extends Fu{
Zi(){
//第一行隐士 super();
System.out.println("子无参")
}
Zi(int age){
System.out.println("子有参")
}
}
class Test{
public static void main(String[] args) {
Zi zi = new Zi();
Zi zi1 = new Zi(3);
//控制台输出结果:Fu无参;子无参;Fu无参;子有参;
}
}
为什么子类一定要访问父类中的构造函数?
因为父类中public的子类可以直接获取,所以子类对象在建立时,需要先查看父类是如何对这些数据进行初始化的。所以子类在对象初始化时,要先访问一下父类的构造函数。
如果访问父类中指定的构造函数,可以通过手动定义super()语句的方式来指定。
注意:
super()语句一定定义在子类构造函数的第一行。
final关键字(最终)
final 可以修饰类,方法,变量(为了避免被继承,被子类复写)
final 修饰的类不可以被继承
final 修饰的方法不可以被覆盖
final 修饰的变量是一个常量,只能被赋值一次,既可以修饰成员变量,也可以修饰局部变量
当在描述事物时,一些数据的出现值时固定的,不需要改变的。所以加上final修饰。
内部类定义在类中的局部位置上时,只能访问该局部被final修饰的局部变量。
class Demo{
final int a = 5; //表示x一只都是5,不能被改变。
public static void main(String[] args) {
final int y = 6;
y = 8;
System.out.println(y);// 我们家上final表示最终的,不能改变。
}
}
?
抽象类
当多个类中出现了相同功能,但是功能主体不同,也可以进行向上抽取,此时只抽取功能定义,而不抽取功能主体。关键字:abstract
特点:
1、抽象方法一定定义在抽象类中;
2、抽象方法和抽象类都要被关键字abstract 修饰;
3、抽象类不能new创建对象,因为调用抽象方法没有意义;
4、抽象类中的抽象方法要被使用,必须由子类复写起所有的抽象方法后,建立子类对象调用。如果子类只覆盖了部分了抽象方法,该子类也还是抽象类。
代码示例:
abstract class Fu{
/* void study(){
System.out.println("FU");
} 因为没有意义,所以使用抽象方法 */
abstract void study();
}
class Zi extends Fu{
void study(){
System.out.println("Zi");
}
}
class Zi1 extends Fu{
void study(){
System.out.println("Zi1");
}
}
抽象类和一般的类没有太大的不同。只不过在抽象类里面我们有一些功能,这些功能不确定具体怎么实现,所以不需要明确的出现,但是所以不需要定义主体,通过我们的抽象方法来实现。
抽象类比一般类多了抽象方法;(抽象只能定义类或方法,不能定义变量)抽象类不能实例化;
练习:有员工类,具有姓名,工号,工资三个属性,经理也有三个属性,但是经理还有奖金的属性,请使用继承的思想设计出员工类和经理类,要求类中提供必要的方法进行属性访问。
模版设计模式
在定义功能时,功能的一部分是确定的 ,但是有一部分是不确定的,而确定的部分在使用不确定的部分,那么这时就将不确定的部分提供出去,由子类去完成。
需求:获取程序开始的时间,运行的时间,结束的时间
abstract class GetTime{
public final void getTime(){
long start = System.currentTimeMillis();//当前时间与协调世界时间1970年1月1日午夜至今的时间差(毫秒为单位测量)
run();
long end = System.currentTimeMillis();
System.out.println("运行毫秒:"+(end - start));
}
public abstract void run();
}
class SubTime extends GetTime{
public void run(){
for(int i = 0 ;i < 1000;i++){
System.out.println(i);
}
}
}
public Test{
public static void main(String[] args){
/*GetTime get = new GetTime(); //此类为抽象类,不能new对象。*/
SubTime get = new SubTime();
get.getTime();
}
}
接口
是什么?
例如我定义了一个接口,但是我在继承这个接口的类中还要写接口的实现方法,那我不如直接就在这个类中写实现方法岂不是更便捷,还省去了定义接口?
接口就是个招牌。
比如说你今年放假出去杭州旅游,玩了一上午,你也有点饿了,突然看到前面有个店子,上面挂着KFC,然后你就知道今天中饭有着落了。
KFC就是接口,我们看到了这个接口,就知道这个店会卖炸鸡腿(实现接口)。
那么为神马我们要去定义一个接口涅,这个店可以直接卖炸鸡腿啊(直接写实现方法),是的,这个店可以直接卖炸鸡腿,但没有挂KFC的招牌,我们就不能直接简单粗暴的冲进去叫服务员给两个炸鸡腿了。
要么,我们就要进去问,你这里卖不卖炸鸡腿啊,卖不卖汉堡啊,卖不卖圣代啊(这就是反射)。很显然,这样一家家的问实在是非常麻烦(反射性能很差)。要么,我们就要记住,中山路108号卖炸鸡,黄山路45号卖炸鸡(硬编码),很显然这样我们要记住的很多很多东西(代码量剧增),而且,如果有新的店卖炸鸡腿,我们也不可能知道(不利于扩展)。
可以认为是一个特殊的抽象类,当抽象类中的方法都是抽象的,那么该类可以通过接口的形式来表示。关键字:interface
接口定义时,格式特点:
1、接口中常见定义:常量,抽象方法;
2、接口中的成员都有固定修饰符。
常量:public、static、final
方法:public abstract
总结:接口中的成员都是public 的;接口时不可以创建对象的,因为有抽象方法。需要被子类实现,子类对接口中的抽象方法全都覆盖后,子类才可以实例化。
示例:
interface Inter{
public static final int NUM = 6;
public abstract void show();
}
class Test implements Inter{
public void show(){
}
}
public Demo{
public static void main(String[] args){
Test t = new Test();
System.out.println(t.NUM);
System.out.println(Test.NUM);
System.out.println(Intert.NUM);
}
}
接口可以被类多实现
示例:
interface Inter1{
}
interface Inter2{
}
class Test implements Inter1,Inter2{
}
接口的特点:
接口是对外暴露的原则;
接口是程序的功能扩展;
接口可以用来多实现;
类与接口至简是实现关系,而且类可以继承一个类的同时实现多个接口;
接口与接口之间可以有继承关系;
第七节 继承、抽象类和接口