一、抽象类
1.1、抽象类的引入
使用类是描述生活中的某一类事物,但是事物和事物之间又有继承的关系,因此我们描述的类和类之间也会有继承关系存在。
在继承中,越往顶层父类,其中描述的属性和行为更加的共性。
我们在描述事物的过程中,把共性的行为或属性不停的往父类中抽取,这样就会导致在父类中描述的行为越来越不具体,无法表示出事物的特有的特点。可是这时我们依然要进行抽取和简单的描述。
描述猫和狗:
猫:
吃鱼
抓老鼠
狗:
啃骨头
看家
狗和猫中有共性的吃的行为,这时我们可以抽取到父类动物类中,但是在动物类中,这个吃的行为的行为体是没有办法写的十分清楚和具体。
这时我们就需要使用Java中的抽象的概念来描述这个无法写清楚的行为。由于行为我们在Java中以函数的形式体现,也就是说这个函数就变成一个抽象的函数,一旦某个类中有了抽象的函数这个类就变成抽象类了。
1.2、抽象类举例
//把狗和猫中的共性抽取到Animal类中
abstract class Animal
{
/*
我们把狗和猫中的eat行为抽取到了Animal类中,
但是我们没有办法去明确具体的函数体代码,
这时不能只写一个空的大括号,如果写了空的大括号
就表示这个函数体是一个空白,但是这个函数有函数体
也就是说当前这个类中的这个函数是可以被调用执行的
我们在描述事物的行为或者功能的时候,
如果没有办法明确具体的函数体的时候这时可以把函数体省略。
没有函数体的函数我们成为抽象的函数,这个函数必须使用Java
中的抽象关键字修饰。
抽象关键字:abstract
当一个类中有抽象方法的时候,这个类必须使用 抽象关键字修饰
*/
abstract void eat();
}
抽象类:
使用某个类描述事物的时候,如果这个类中在描述这个事物的某个行为或功能的时候,只知道这个事物有这个行为或功能,但没有办法书写清楚具体的功能体,函数体,这时可以使用抽象函数来表示这个行为或功能,由于类中有抽象的函数,这个类就会变成抽象类。
当一个类继承某个抽象类,这时这个类要把这个抽象类中的所有抽象方法全部复写掉。如果子类没有把父类中的抽象函数复写完,或者根本就没有复写,这时这个子类还是一个抽象类。
//把狗和猫中的共性抽取到Animal类中
abstract class Animal
{
abstract void eat();
}
class Dog extends Animal
{
void eat()
{
System.out.println("啃骨头");
}
void lookHome()
{
System.out.println("看家");
}
}
class Cat extends Animal
{
//复写Animal类中的抽象函数eat
void eat()
{
System.out.println("吃鱼");
}
void catchMouse()
{
System.out.println("抓老鼠");
}
}
class AbstractDemo
{
public static void main(String[] args)
{
Cat c = new Cat();
c.eat();
//演示多态
Animal a = new Cat();
a.eat();
}
}
1.3、抽象类的细节(重点)
1、抽象类中的抽象函数需要子类全部复写,我们不写抽象类更好,直接在子类中写这些函数。
后期我们大量的时间是学习如何去使用已经存在的类,然后创建这个类或者子类的对象,调用其中的功能,完成我们的需求。
在Java中使用类,一般我们都会去先查阅这个类继承体系中的顶层父类中的功能,如果顶层类中的功能已经可以满足我们的需求,这时不会关注具体哪个子类。一般在继承体系的顶层,描述的功能都是不具体的功能,也就是说我们是无法直接调用这些不具体的功能,开发中的做法是,创建顶层这个父类的引用,然后随便new一个子类对象(多态),然后就开始使用这个父类的引用,调用我们看到的函数了。
抽象类中定义所有子类的共性行为,然后由具体的子类针对自己的实际情况复写父类提供的基本行为,建立适合自己的功能体。
2、抽象类和一般的类什么区别:
抽象类肯定需要abstract修饰,一般类不能使用。
不管是抽象类,还是一般类,它们都是描述事物体系中的某个事物,只不过抽象类描述的一定是这个事物体系中的共性内容,抽象类一定是顶层的类,一定不是最底层的类。
3、抽象类有没有构造函数,能不能创建对象?
有构造函数,但不能创建对象。
抽象类一定是个类,类中肯定就会有构造函数。抽象类不能创建对象的原因是抽象类中有抽象函数,如果我们可以直接创建抽象类的对象,那么就可以使用这个对象调用哪个抽象函数,而抽象函数是没有函数体的函数,调用函数的最终目的是需要函数体的执行,完成我们想要的结果。因此我们如果可以创建抽象类的对象,那么就会导致可以调用抽象函数,而调用了抽象函数,却得不到任何想要的效果。这样的做法没有意义。因此sun公司在指定抽象类的规则时,不让程序直接创建抽象类的对象。
1.4、抽象类的特点
1、抽象类一定是父类吗?
一定是父类,但不一定是顶层父类。
抽象类中通常都有抽象函数,而抽象类中的抽象函数要求必须由子类来复写(由具体的子类来实现其中的函数体)。
子类的主要作用是复写抽象类中的抽象函数。
2、抽象类可以继承其他类吗?
抽象类还是一个类,因此它必须具备类的继承特点。它可以有父类。
3、抽象类中有构造函数吗?有什么作用?
有构造函数。但是这个类不能创建对象。因为抽象类一定有子类,而创建子类对象的时候,在子类的构造函数中一定有super语句会找自己的父类构造函数进行初始化动作。
4、抽象类中可以没有抽象函数吗?
可以。它的存在意义就是不让外界创建当前这个类的对象。这样的类符合的设计模式中的适配器模式。
5、抽象关键字不能和哪些关键字共存?互斥
private :父类中私有的成员子类是不知道的,因此使用private 和abstract关键字一起修饰函数,导致子类根本无法知道父类中有个抽象函数,而且需要子类复写。
static:如果使用static和abstract关键字一起修饰抽象函数,导致这个函数可以使用类名直接调用,而调用抽象函数是没有意义的。
final :final修饰的函数子类是无法复写的,而abstract修饰的函数,要求子类必须复写。
抽象类何时使用:
当描述事物体系,一般在描述所有体系中最共性的内容时,通常是只知道体系的共性功能,却不能书写具体功能体,这时会使用抽象函数表示,那么这个类一定会使用抽象类表示。
二、接口
接口有2个功能:
- 给继承体系增加额外(扩展)功能。
- 可以给双方定义规则,约束双方。
接口的定义:
interface 关键字定义。
接口中的成员:
成员变量 有固定的 修饰符 public static final
成员函数 有固定的 修饰符 public abstract
类和类之间的关系:继承,并且只能单继承。
类和接口之间的关系:实现,并且可以多实现。
接口和接口之间的关系:继承,可以多继承。
2.1、接口的引入
描述 猫和 狗 ,这2类事物 它们一定具备 动物这个体系的基本共性行为。
但是猫和狗经过后天的训练 , 可以具备一些本身不属于这个事物体系中的额外的功能。而这时假设猫和狗都具备了这个行为,也是相同的行为在不同的事物中同时存在,而在Java的设计中,只要是多个类中有共性的内容,我们就可以进行共性的抽取描述。而现在猫和狗经过训练而具备的后天的额外功能,不属于动物体系本身固有的行为。无法抽取到动物类中。
可是我们又要进行抽取,那么就可以使用Java中提供的接口来完成。
接口的定义格式:
类的定义格式:
修饰符 class 类名
{
}
接口的定义格式:
interface 接口名
{
}
2.2、接口的代码体现
//演示接口的简单使用
//把狗 猫 猪中的共性行为抽取到Animal中
abstract class Animal
{
abstract void eat();
}
假设猫和狗经过后天的训练具备 缉毒的功能
这样导致猫和狗中有共性的行为,需要抽取,这时不能抽取到Animal中。
发现缉毒功能不属于动物体系的共有行为,这时又要把这个行为进行抽取,
于是我们就可以把这些功能抽取到 接口中
把缉毒抽取到接口中之后,在接口中应该也没有办法把缉毒的行为描述具体,也应该使用抽象函数表示
让类和接口之间产生关系:
类和类之间的关系是继承,使用extends关键字完成
类和接口之间的关系是实现,使用的 implements 关键字完成
interface 缉毒
{
abstract void 缉毒();
}
//狗既具备了动物的固有功能,同时也具备了自己的额外功能
class Dog extends Animal implements 缉毒
{
void eat()
{
System.out.println("啃骨头");
}
void lookHome()
{
System.out.println("看家");
}
public void 缉毒()
{
System.out.println("狗缉毒");
}
}
class Cat extends Animal implements 缉毒
{
void eat()
{
System.out.println("吃鱼");
}
void catchMouse()
{
System.out.println("抓老鼠");
}
public void 缉毒()
{
System.out.println("猫缉毒");
}
}
class Pig extends Animal
{
void eat()
{
System.out.println("吃糠");
}
void gongDi()
{
System.out.println("拱地");
}
}
2.3、接口中成员的特点
在类的成员位置上可以定义:成员变量、函数、构造函数、静态代码块、构造代码块。
在接口的成员位置上:成员变量,和成员函数。
接口不是类,它的出现仅仅是给描述这个事物体系增加额外的共性功能。因此在接口中是没有构造函数的。也就是说接口是不能new对象的。
接口中的所有成员都具有固定的修饰符:
接口中的成员变量修饰:
public static final ,这个三个修饰符 可以省略不写。
接口中的变量,都是常量。因此接口中的变量名一般都会全部大写。
接口中的成员函数:
public abstract 返回值类型 函数名( 参数列表 );
public abstract 可以省略,但是建议可以省略 abstract ,不要省略 public
在Java中只支持类的单继承,不支持多继承。而Java对这个多继承进行改良,修改成了多实现。
一个类在继承一个父类的同时,可以实现多个接口。
interface A
{
void show();
}
interface B
{
void show();
}
interface C
{
void show();
}
interface D
{
void show();
}
class Fu
{
}
class Demo extends Fu implements A,B,C,D
{
public void show()
{
}
}
A f = new Demo();
f.show();
多态:
一个子类的对象赋值给了父类的引用,或者赋值给了接口的引用,都属于多态。
一个类实现多个接口,这时由于接口中的函数都是抽象函数,只有子类实现了接口中的所有函数之后,最后我们需要创建实现类的对象,最后执行的函数,还是实现类中实现的函数,不管有多少个接口,即使它们的函数同名,由于都是抽象的,不会发生任何问题,最后运行还是实现类中函数。
但是如果接口中有多个同名的成员变量,这时在实现类中使用接口中的成员变量,就会发生调用的不确定性。
因此后期再定义接口的时候,多个接口中尽量不要有同名的成员变量。
总结:
类和类:继承关系,一个类只能继承一个父类。
类和接口:实现关系,一个类可以实现多个接口。
接口和接口:继承关系,一个接口可以继承多个接口。
类和类之间是继承,存在子类和父类的关系。子类需要覆盖父类的函数时,我们称为函数的复写、覆盖、重写。
类和接口时间是实现关系,实现接口的类,一般称为实现类,实现类中对接口中的函数进行重写的书写。这时称为实现类对接口的方法进行的实现。
三、抽象类和接口
3.1、接口和抽象类的使用
需求:数据建模
描述程序员和项目经理
分析:
程序员:
属性:
姓名
年龄
部门
薪水
行为:
编码
项目经理:
属性:
姓名
年龄
部门
薪水
奖金
行为:
管理
//程序员和经理的共性内容抽取到父类雇员这个类中
abstract class Employee
{
private String name;
private int age;
private String dept;
private double salary;
Employee( String name , int age , String dept , double salary )
{
this.name = name;
this.age = age;
this.dept = dept;
this.salary = salary;
}
//程序员和经理都需要工作,但工作的内容不同,这时在雇员这个类中只能使用抽象函数表示
abstract void work();
}
//可以把培训行为抽取到接口中
interface Inter
{
void peiXun();
}
//程序员类
class Programmer extends Employee implements Inter
{
//对外提供构造函数,对程序员对象进行初始化
Programmer( String name , int age , String dept , double salary )
{
//通过super 把共性的属性交给父类进行初始化
super(name,age,dept,salary);
}
//程序员实现工作的行为
void work()
{
System.out.println("编代码.....");
}
//程序员具备的培训功能
public void peiXun()
{
System.out.println("培训其他的新入职员工");
}
}
//经理类
class Manager extends Employee
{
//奖金
private double comm;
Manager( String name , int age , String dept , double salary , double comm )
{
super(name,age,dept,salary);
this.comm = comm;
}
//经理实现工作的行为
void work()
{
System.out.println("管理...程序员..");
}
}
class InterfaceTest
{
public static void main(String[] args)
{
Manager m = new Manager("荆小六",19,"项目部",8888,10000);
m.work();
//Programmer p = new Programmer("楚乔",22,"开发部",9999);
//p.work();
//要求所有的培训师做事,关注培训这个功能
Inter i = new Programmer("楚乔",22,"开发部",9999);
i.peiXun();
}
}
后期在使用其他已经存在的类和接口的时候,一定要从最顶层开始查阅,只要顶层能满足,这时子类或者实现类中肯定会对这些功能进行自己的复写或实现。那么我们就一定会使用多态的技术来操作。
使用父类的引用或接口的引用 指向 自己的子类或实现类对象。
3.2、抽象类和接口的区别
接口和抽象类都是描述事物的共性行为,并且描述的行为一般都是抽象的。需要子类或实现类对这些行为进行实现或复写。
抽象类:它依然描述的事物体系中一类。而接口描述的共性行为是事物体系的额外功能(扩展功能)。
抽象类依然是类,属于整个事物体系中一种,而接口不属于这个事物体系。
四、没有抽象函数的抽象类
如果一个接口中有多个函数,而实现类仅仅只需要其中部分,或则个别函数。这时由于实现类实现了接口,就必须对接口中的所有函数进行实现。
如果这个实现没有把接口中的所有函数全部实现,那么这个实现类中就隐藏这个抽象函数,那么这个实现类就变成抽象类。外界就无法直接创建这个类的对象,调用已经实现了的函数。
当用户需要使用一个接口中的部分函数,或者个别函数时,而不是接口中的全部函数,那么要求定义这个接口的人,给这个接口定义一个实现类,然后把接口中的函数全部给空实现(函数体什么都不写)。而当外界需要使用接口中的函数时,不要直接去实现接口,而去继承这个实现类,然后复写需要使用函数即可。
interface Inter
{
public void show1();
public void show2();
public void show3();
public void show4();
public void show5();
public void show6();
public void show7();
public void show8();
}
//可以对这个接口定义一个实现类
abstract class AdapterImpl implements Inter 适配器类
{
public void show1(){}
public void show2(){}
public void show3(){}
public void show4(){}
public void show5(){}
public void show6(){}
public void show7(){}
public void show8(){}
}
//假设只需要使用show1 和show2方法
class InterImpl extends AdapterImpl
{
public void show1()
{
System.out.println("show1......");
}
public void show2()
{
System.out.println("show2......");
}
}
4.1、适配器设计模式
在设计接口的时候,如果接口中的函数过多,这时需要给接口定一个实现类,这个实现类称为适配器类,这个类中的函数都是空实现,函数体中没有任何代码。这时这个适配器类需要定义成抽象类。
适配器类主要的作用是从 接口 过渡到 真实类。
适配器三元素:
1、接口(多余的方法)
2、适配器类(抽象类)
3、适配器类的集成和方法重写(继承抽象类的实体类)