今天学习的内容是抽象类与接口
一、抽象类和抽象方法
之前说到继承,子类不仅包含父类的所有内容,还有自己的新功能,那么实例化父类就显得没有意义了。在实际工作中,一般将父类定义为抽象类。有时没有办法作出给任何子类都有意义的共同程序代码,就可以把它定义为抽象方法。抽象类和抽象方法的知识点如下:
- 被abstract关键字修饰的类叫做抽象类,被abstract关键字修饰的方法叫做抽象方法
- 抽象类格式:abstract class 类{}
- 抽象方法格式:abstract 返回值类型 方法();
- 抽象类不能被实例化,因为调用抽象类对象的抽象方法没有意义
- 抽象类中可以定义抽象方法和具体方法,可以全都是抽象方法,也可以全都是具体方法
- 抽象类中全都是具体方法的目的就是不让该类创建对象,通常这个类中的方法都有方法体但是没有具体内容,所以创建该类对象没有意义
- 只要类中至少有一个抽象方法存在,它就必须定义为抽象类
- 抽象类中存在构造函数,是为了给父类子对象进行初始化
- 与普通的类和方法一样,抽象类和抽象方法也被权限修饰符控制(抽象类只能是public或包访问权限)
- abstract关键字不能和private、static和final关键字共存,这是因为这三种修饰符所修饰的方法压根没法被覆盖!!而没法被覆盖就更没法多态!!
- 如果某类继承了抽象类,并想创建子类对象,那么就必须实现抽象类中所有抽象方法。如果不这么做也可以,子类便也是抽象类,必须加上abstract关键字,否则编译报错
abstract class Fu{
//抽象类中也有构造函数,是为了给父类的子对象进行初始化
public Fu(){
System.out.println("父类构造函数");
}
//abstract不能和private、static和final共存
//!private abstract void test();
//!static abstract void test();
//!final abstract void test();
//抽象类中可以定义抽象方法和具体方法,可以都是抽象方法,也可以都是具体方法
public abstract void absA();
public abstract void absB();
public void aa(){
System.out.println("父类的aa()方法");
}
}
public class Zi extends Fu{
//实现父类的抽象方法absA()
public void absA() {
System.out.println("子类实现父类的抽象方法absA");
}
//实现父类的抽象方法absB()
public void absB() {
System.out.println("子类实现父类的抽象方法absB");
}
//覆盖父类的方法aa()
public void aa(){
System.out.println("子类的aa()方法");
}
public static void main(String[] args){
//如果某类继承了抽象类,并想创建子类对象,那么就必须实现抽象类中所有抽象方法
Zi z=new Zi();//会调用抽象类构造函数
z.aa();//子类的aa()方法
z.absA();//子类实现父类的抽象方法absA
z.absB();//子类实现父类的抽象方法absB
//!Fu fu=new Fu(); 抽象类不能被实例化,因为调用抽象方法没意义
}
}
二、接口
- 由interface修饰的类叫做接口
- 语法格式:interface 接口名{}
- 接口被权限修饰符控制,可以是public和包访问权限
- 接口中的所有变量都被隐式定义为public、static和final的
- 接口中所有方法都被隐式定义为public和abstract的
- 实现接口的类,除非实现了所有抽象方法,否则必须加abstract
- 接口解决了多继承的“致命方块”问题(因为实现类只会有一个该方法 ,不存在不清楚调用哪个的问题)
- 接口支持多实现,一个类可以同时继承单个父类与实现多个接口
- 实现的格式为:class 接口 implements 接口1,接口2...{}
- 接口与接口之间是继承关系,而且可以多继承
interface JieKou1{
String STR="123";//接口中的变量自动是public、static、final的,
void aa();//接口中的方法自动是public、abstract的
}
interface JieKou2{
void bb();
}
//接口与接口之间是继承关系,而且可以多继承
interface JieKou3 extends JieKou1,JieKou2{}
abstract class Fu{
abstract public void cc();
}
//一个类可以同时继承单个父类与实现多个接口
public class Impl extends Fu implements JieKou1,JieKou2{
@Override
public void aa() {
System.out.println("实现第一个接口的aa()方法");
}
@Override
public void bb() {
System.out.println("实现第二个接口的bb()方法");
}
@Override
public void cc(){
System.out.println("覆盖父类的cc()方法");
}
public static void main(String[] args){
Impl impl=new Impl();
impl.aa();//实现第一个接口的aa()方法
impl.bb();//实现第二个接口的bb()方法
impl.cc();//覆盖父类的cc()方法
System.out.println(JieKou1.STR);//123
}
}
三、使用场景
组合、继承和接口到底应该什么时候使用呢?
之前使用Spring+SpringMVC+Mybatis框架做项目时,我发现项目中使用最多的是组合还有接口。从实际出发,我总结了一下这三种技术的使用场景(个人愚见)
组合:目的只是复用代码,将现有类的对象引用置于本类中即可。其实组合主要是为了调用第三方API。。
接口:目的是建立规范和解耦。
比方说开会,会议内容一定是先致辞、然后讨论、最后总结,但是你并不知道每个步骤具体要做什么。这时可以将会议定义为一个接口,这个接口有致辞、讨论和结束三个方法(建立规范)。首先你要实现会议接口,这个实现类就是符合规范的会议;然后在程序中用接口会议的引用指向传入的实现类会议对象(向上转型),制定规范的人不用知道每个会议具体是如何实现的(解耦),直接调用对象的方法即可(运行时多态)。
继承:目的是复用代码和扩展新功能。还是上面的例子,如果已经有了一个会议,你不仅想要保留这个会议的内容(复用代码),还想增加新内容比如发奖金(扩展新功能)。这时就可以使用继承,将已有的会议继承,该覆盖的覆盖,并实现定义了发奖金方法的接口。然后在程序中用父类会议的引用指向传入的子类会议对象(向上转型),直接调用对象的方法即可(运行时多态)。
关于接口,我还在知乎上看到一种不错的比喻:
接口就是个招牌。
比如说你今年放假出去杭州旅游,玩了一上午,你也有点饿了,突然看到前面有个店子,上面挂着KFC,然后你就知道今天中饭有着落了。(实现KFC接口的类会被视作KFC)
KFC就是接口,我们看到了这个接口,就知道这个店会卖炸鸡腿(实现接口的方法)。
那么为神马我们要去定义一个接口涅,这个店可以直接卖炸鸡腿啊(直接写方法),是的,这个店可以直接卖炸鸡腿,但没有挂KFC的招牌,我们就不能直接简单粗暴的冲进去叫服务员给两个炸鸡腿了。
要么,我们就要进去问,你这里卖不卖炸鸡腿啊,卖不卖汉堡啊,卖不卖圣代啊(这就是反射)。很显然,这样一家家的问实在是非常麻烦(反射性能很差)。
要么,我们就要记住,中山路108号卖炸鸡,黄山路45号卖炸鸡(硬编码),很显然这样我们要记住的很多很多东西(代码量剧增),而且,如果有新的店卖炸鸡腿,我们也不可能知道(不利于扩展)。
顺便说一下,接口分为广义和狭义两个意思:狭义指的是Java中的interface;广义指的就是应用程序接口--API,也就是通过调用某些方法获得应用程序的功能,比方说我们自己写的public方法和第三方库的public方法,都可以视作API。不仅是在程序中调用,在实际开发中的所谓后端数据接口(RESTful API)也可以视作API,其原理就是通过调用Server端程序的表现层方法,获得其需要的功能(俗称的后台接口)。
面试题:
1.接口和抽象类?
答:
1.一个类可以实现多个接口,但只能继承一个抽象类(除非将接口或抽象类的所有抽象方法全都实现,否则就得用abstract修饰)
2. 接口默认使用abstract+public修饰抽象方法
3. 抽象类中可以定义抽象方法和具体方法;接口中可以定义抽象方法、默认方法和静态方法
4. 接口的属性是默认的public static final
5. 抽象类中可以含有静态代码块和静态方法,而接口可以含有静态方法,但不能含有静态代码块。
6. 抽象类可以含有构造方法,接口不能含有构造方法。