广州番禺Python, Java小班周末班培训
第四期线下Java, Python小班周末班已经开课了,培训的课程有Python爬虫,Python后端开发,Python办公自动化,Python大数据分析, Java中高级工程师培训。授课详情请点击:http://chipscoco.com/?cate=6
8.4.1 接口的定义
在9.3.4节中提及了接口,接口与类的作用大致相同:用来提供基本的抽象。但二者在语义上还是有区别的,类是对共有属性、行为的抽象,而接口侧重于对行为进行抽象。严格来说,子类继承父类需在语义上符合is-a关系,即子类和父类必需是同一类事物,比如父类是猫,子类也必需是猫。 思考题,如果子类是机器猫,是否违背继承的语义?
机器猫具有猫的外形,亦具有猫的行为(模拟猫的叫声),在AI的帮助下,甚至会抓老鼠。
但机器猫并不是猫,此时应该这么来设计,定义一个机器类,定义一个机器猫类,以及定义一个与猫抓老鼠行为相关的捕手接口。机器猫继承于机器类,同时实现捕手接口。现在我们开始来学习Java中的接口。以下是定义Java接口的基本语法:
[public]interface Interface [extends ParentInterface]{ [public] [static] [final] property=value; [public] [abstract] data_type methodName(parameters_list); }
小写的interface是Java中的关键字,用来定义一个接口。Interface表示接口名,在命名接口时,通常采用驼峰式命名法。extends关键字用来表示继承关系,在Java中,接口可以继承接口,ParentInterface表示父接口。
Java中的类只能继承于类,有且仅有一个父类。同样地,Java中的接口只能继承接口,有且仅有一个父接口。
在接口中也可以定义属性,但属性通常是以常量的形式来定义使用,默认用public static final来进行修饰,即公有,静态,不可修改。而接口中的方法默认用public abstract来修饰,即在默认情况下,接口中的方法都是公有的抽象方法。现在根据接口的语法来定义一个捕手接口,该接口提供了一个抓老鼠的抽象方法:
// 定义一个Catcher接口,用来提供与捕捉相关的行为抽象 interface Catcher { // 接口中的方法默认是抽象方法 void catchMouse();
8.4.2 接口的实现
定义好了接口以后,需要再实现接口。在Java中通过implements关键字来实现接口。使用implements实现接口的基本语法:
[modifier] class ClassName [extends BaseClass] [implements interface1,interfac2,...]{ // {}里面的为类体 ; }
语法前面的一部分系定义类的基本语法,不再赘述。主要是后面的implements那一部分,在Java中只能通过类来实现接口,且一个类可以同时实现多个接口。类实现多个接口时,接口之间以英文逗号进行分隔。现在我们将9.4.1节中的机器猫例子写出来,定义一个机器类,机器猫类,然后让机器猫实现捕手接口:
// 定义一个Catcher接口,用来提供与捕捉相关的行为抽象 interface Catcher { // 接口中的方法默认是抽象方法 void catchMouse(); } // 定义抽象基类Machine,提供run的抽象方法 abstract class Machine { abstract void run(); } // 机器猫本质是机器,所以继承于Machine类 class MachineCat extends Machine implements Catcher{ void run(){ System.out.println("机器猫已启动"); } public void catchMouse(){ System.out.println("我也能抓老鼠,难道我只能是机器吗?"); } } public class HelloJava { public static void main(String[] args) { MachineCat doraemon = new MachineCat(); // 调用run方法来启动机器猫 doraemon.run(); // 调用catchMouse接口来抓老鼠 doraemon.catchMouse(); } }
Java中的类有且仅有一个父类,但却可以同时实现多个接口,现在修改上面的代码,新增一个Cryable接口,使得机器猫不仅能抓老鼠,还会发出像猫一样的怪叫声。
// 定义一个Catcher接口,用来提供与捕捉相关的行为抽象 interface Catcher { // 接口中的方法默认是抽象方法 void catchMouse(); } // 定义一个Cryable接口,用来提供与动物叫声相关的行为抽象 interface Cryable { // 供类来实现不同的cry行为 void cry(); } // 定义抽象基类Machine,提供run的抽象方法 abstract class Machine { abstract void run(); } // 机器猫同时实现Catcher接口和Cryable接口 class MachineCat extends Machine implements Catcher, Cryable{ void run(){ System.out.println("机器猫已启动"); } public void catchMouse(){ System.out.println("我也能抓老鼠,难道我只能是机器吗?"); } public void cry(){ System.out.println("机器猫发出了像猫一样的怪叫声:miaow...miaow"); } } public class HelloJava { public static void main(String[] args) { MachineCat doraemon = new MachineCat(); // 调用run方法来启动机器猫 doraemon.run(); // 调用catchMouse接口来抓老鼠 doraemon.catchMouse(); // 调用cry接口发出猫叫声 doraemon.cry(); } }
类在实现多个接口时,会同时具有多个接口的类型,利用这样的特性以及Java的多态机制,可以写出更为灵活的代码。
8.4.3 抽象类与接口
在9.4.2节中,笔者有意将Machine类设计为抽象基类。读者如果细心对比Machine类与Catcher接口,会发现两者并无太大区别:它们提供的均为行为的抽象,且提供的方法都由子类去实现。一些读者可能有疑惑,既然区别不大,那为什么Java还要引入接口的机制?其实抽象类与接口存在着较大的区别。
首先第一点,Java中的接口都是抽象方法,必需由类来实现,而抽象类既可以定义抽象方法,也可以定义非抽象方法。Java中的接口主要提供的是一系列行为的抽象,在语义上更为明确和纯粹。抽象基类新增一个非抽象方法时,子类会自动继承,不会影响代码的编译和执行。但如果接口新增一个方法,那么实现该接口的类必需实现该方法(因为接口中的方法只能是抽象方法),否则会编译报错。
其次第二点,抽象类本质还是类,子类在继承抽象类时需在语义上符合is-a关系。而类在实现接口时,通常是用来混合一个于继承体系之外的行为。以本节的机器类和机器猫类为例。机器猫本质是机器,具有机器的共性,所以应该从机器类中继承。但是抓老鼠和发出猫叫的行为,却不能通过继承获得(因为机器猫并不是猫)。此时就应该通过接口来混合一个新的类型,使得机器猫既具有机器的共性,又可以获得像猫一样的行为。
8.4.4 接口的多态
Java的接口天然支持多态,因为接口中的方法都为抽象方法,必须由其它类来实现,而且类在实现接口方法时,不需要考虑这种类继承的is-a关系,所以接口的多态对于类的多态来说更为泛型。接口的多态实例-猫鼠齐飞:
// 定义一个Flyable接口,用来提供与飞行相关的行为抽象
interface Flyable{
void fly();
}
// 定义抽象基类Machine,提供run的抽象方法
abstract class Machine {
abstract void run();
}
// 机器猫实现Flyable接口
class MachineCat extends Machine implements Flyable{
void run(){
System.out.println("机器猫已启动");
}
public void fly(){
System.out.println("猫飞起来了");
}
}
// 老鼠实现Flyable接口
class Mouse implements Flyable {
public void fly(){
System.out.println("老鼠飞起来了");
}
}
public class HelloJava {
public static void main(String[] args) {
/*
接口的多态不局限于同一类型,机器猫与老鼠都实现了Flyable接口,
读者需深入理解类的多态与接口的多态的区别
*/
Flyable everyoneCanFly = new MachineCat();
everyoneCanFly.fly();
everyoneCanFly = new Mouse();
everyoneCanFly.fly();
}
}
8.4.5 面向接口编程
在笔者看来,狭义的面向接口编程就是本节所讲的接口设计。由于Java中的接口提供的都为抽象方法,所以必需由其它类来实现,这样设计有一个很明显的优点,将接口的定义和实现隔离。
隔离是一种松耦合思想,客户端在使用接口时,只需关注接口提供了哪些方法。
定义接口就相当于制定一套行为规范,类在实现接口时,可以有不同的实现细节,但必需遵循统一的接口规范,这样可以保证接口的易用性和一致性。面向接口编程还体现在接口的多态特性,类在实现多个接口时就同时具有了多个接口的类型,利用多态,就可以编写出低冗余,高扩展的代码。广义的面向接口编程是更为高层的抽象,不会局限于某一类编程语言,感兴趣的同学,可以深入学习设计模式和系统架构。