前面介绍了抽象方法及抽象类的用法,看似解决了不肯定行为的方法定义,既然叫唤动做容许声明为抽象方法,那么飞翔、游泳也能声明为抽象方法,而且鸡类涵盖的物种不够多,最好把这些行为动做扩展到鸟类这个群体,因而整个鸟类的成员方法均可以如法炮制了。但是这种作法也带来了一些弊端,包括但不限于:
一、能飞的动物不只仅是鸟类,还有昆虫、蝙蝠等其它动物也能飞,难不成昆虫类、哺乳动物类也要自行声明飞翔方法?这么作显然产生了重复的方法定义。否则的话,要是把飞翔方法挪到更底层的动物类,一大群动物为了避免沦为抽象类都得重写飞翔方法,好比鳄鱼、大象等根本不会飞的动物也要装模做样扑腾几下,实在是滑天下之大稽。
二、除了几种常见的鸟类为大众所熟知以外,大部分鸟类其实人们一时半刻叫不出它们的名字,假若在路上偶遇一只鸟儿,难道由于不认识它就无法描述它的模样了吗?(若是鸟类是个抽象类,外部是不能建立鸟类实例的)
三、就算给整个动物类都添加了叫唤、飞翔、游泳这些抽象方法,而且费尽九牛二虎之力把全部派生而来的子类都实现了这三个抽象方法,也不意味着万事大吉。譬如青蛙擅长跳跃这个动做,哪天程序员突发奇想要给抽象的动物类补充跳跃方法,从而支持青蛙的跳跃行为,随之而来的代价即是让动物类的全部子类都重写跳跃方法,这样也太伤筋动骨了。
综上所述,抽象类解决不了层出不穷的问题,远非什么灵丹妙药,只能用于处理符合条件的特定要求。若想真正有效应对这些刁钻古怪的挑战,还得期望新的抽象技术,在Java编程中这就是接口。接口不从属于类,而是与类平级,类经过关键字class标识,而接口经过关键字interface来标识。因为接口是做为类的辅助角色出现,所以它在结构上与类比较类似,不过也有很多不一样之处,举例以下:
一、凡是类都有构造方法,即使是抽象类也支持定义构造方法,但接口不容许定义构造方法,由于接口只用于声明某些行为动做,自己并不是一个实体。
二、在Java8之前,接口内部的全部方法都必须是抽象方法,具体的方法内部代码有赖于该接口的实现类来补充。由于有这个强制规定,因此接口内部方法的abstract前缀可加可不加,即便不加abstract,编译器也会默认把该方法看成抽象方法。
三、至于接口内部的属性,则默认为终态属性,即添加了final前缀的成员属性。固然这个final前缀也是可加可不加的,即便不加final,编译器仍会默认把该属性看成终态属性。
按照上述的接口规定,再来编写一个定义了动物行为的接口代码,其中主要包括飞翔方法、游泳方法、奔跑方法等,详细的接口定义代码示例以下:html
//定义一个接口。接口主要声明一些特定的行为方法 public interface Behavior { // 声明了一个抽象的飞翔方法。注意,接口内部的方法默认都是抽象方法,因此能够不用添加abstract前缀 public void fly(); //abstract public void fly(); // 这里的abstract可加可不加 // 声明了一个抽象的游泳方法 public void swim(); // 声明了一个抽象的奔跑方法 public void run(); // 接口内部的属性默认都是终态属性,因此能够不用添加final前缀 public String TAG = "动物世界"; //public final String TAG = "动物世界"; // 这里的final可加可不加 // 接口不容许定义构造方法。在Java8之前,接口内部的全部方法都必须是抽象方法 }
接着定义一个鹅类,它不但继承自Bird鸟类,并且实现了新的行为接口Behavior。注意子类继承父类的格式为“extends 父类名”,实现某个接口的格式则为“implements 接口名”,同时该类还要重写接口里的全部抽象方法。因而实现了行为接口的鹅类代码以下所示:java
//定义一个实现了接口Behavior的鹅类。注意鹅类须要实现Behavior接口的全部抽象方法 public class Goose extends Bird implements Behavior { public Goose(String name, int sexType) { super(name, sexType); } // 实现了接口的fly方法 @Override public void fly() { System.out.println("鹅飞不高,也飞不远。"); } // 实现了接口的swim方法 @Override public void swim() { System.out.println("鹅,鹅,鹅,曲项向天歌。白毛浮绿水,红掌拨清波。"); } // 实现了接口的run方法 @Override public void run() { System.out.println("槛外萧声轻荡漾,沙间鹅步满蹒跚。"); } }
对于外部来讲,这个鹅类跟通常的类没啥区别,鹅类所实现的接口方法,在外部看来都是鹅类的成员方法,原来怎么调用如今依然怎么调用。下面是外部使用鹅类的代码例子:程序员
// 演示简单接口的实现类用法 private static void testSimple() { Goose goose = new Goose("家鹅", 0); goose.fly(); // 实现了接口的fly方法 goose.swim(); // 实现了接口的swim方法 goose.run(); // 实现了接口的run方法 }
接口与类相比还有一个重大区别,在Java体系之中,每一个类最多只能继承一个父类,不能同时继承多个类,也就是不容许多重继承。而接口不存在这方面的限制,某个类能够只实现一个接口,也能够同时实现两个接口、三个接口等等,待实现的接口名称之间以逗号分隔。例如除了飞翔、游泳、奔跑这三种动做以外,有些动物还擅长跳跃,好比青蛙、袋鼠等等,假若在现有的Behavior接口中增长跳跃方法jump,那么包括Goose在内全部实现了Behavior的类都要重写jump方法,显然改造量巨大。如今借助接口的多重实现特性,彻底能够另外定义新的行为接口Behavior2,在新接口中声明跳跃方法,那么只有实现Behavior2接口的类才须要重写jump方法。按此思路单独定义的新接口Behavior2代码见下:编程
//定义另外一个行为接口 public interface Behavior2 { // 声明了一个抽象的跳跃方法 public void jump(); }
而后编写Frog蛙类的定义代码,这个蛙类同时实现了接口Behavior和Behavior2,这样它要重写Behavior的三个方法,以及Behavior2的跳跃方法。下面是一个蛙类代码的简单例子:ide
//定义一个实现了接口Behavior和Behavior2的蛙类。类只能继承一个,但接口能够实现多个 public class Frog implements Behavior, Behavior2 { // 实现了Behavior2接口的jump方法 @Override public void jump() { System.out.println("青蛙跳跃的技能叫作“蛙跳”"); } // 实现了Behavior接口的fly方法 @Override public void fly() { } // 实现了Behavior接口的swim方法 @Override public void swim() { System.out.println("青蛙游泳的技能叫作“蛙泳”"); } // 实现了Behavior接口的run方法 @Override public void run() { } }
因为新增的jump方法属于新接口Behavior2,不属于原接口Behavior,所以实现了Behavior接口的鹅类代码无需任何修改,只有实现Behavior2的蛙类代码才需额外处理。固然这个特殊处理也仅限于蛙类的定义,对于外部而言,蛙类Frog还是一个普通的类,外部调用它并无什么两样,具体的调用代码示例以下:htm
// 演示某个类同时实现了多个接口 private static void testMultiple() { Frog frog = new Frog(); frog.swim(); // 实现了Behavior接口的swim方法 frog.jump(); // 实现了Behavior2接口的run方法 }
更多Java技术文章参见《Java开发笔记(序)章节目录》blog