Java开发笔记(五十八)简单接口及其实现

前面介绍了抽象方法及抽象类的用法,看似解决了不确定行为的方法定义,既然叫唤动作允许声明为抽象方法,那么飞翔、游泳也能声明为抽象方法,并且鸡类涵盖的物种不够多,最好把这些行为动作扩展到鸟类这个群体,于是整个鸟类的成员方法都可以如法炮制了。可是这种做法也带来了一些弊端,包括但不限于:
1、能飞的动物不仅仅是鸟类,还有昆虫、蝙蝠等其它动物也能飞,难不成昆虫类、哺乳动物类也要自行声明飞翔方法?这么做显然产生了重复的方法定义。不然的话,要是把飞翔方法挪到更底层的动物类,一大群动物为了不沦为抽象类都得重写飞翔方法,比如鳄鱼、大象等根本不会飞的动物也要装模作样扑腾几下,实在是滑天下之大稽。
2、除了几种常见的鸟类为大众所熟知之外,大部分鸟类其实人们一时半刻叫不出它们的名字,倘若在路上偶遇一只鸟儿,难道因为不认识它就没法描述它的模样了吗?(如果鸟类是个抽象类,外部是不能创建鸟类实例的)
3、就算给整个动物类都添加了叫唤、飞翔、游泳这些抽象方法,并且费尽九牛二虎之力把所有派生而来的子类都实现了这三个抽象方法,也不意味着万事大吉。譬如青蛙擅长跳跃这个动作,哪天程序员突发奇想要给抽象的动物类补充跳跃方法,从而支持青蛙的跳跃行为,随之而来的代价便是让动物类的所有子类都重写跳跃方法,这样也太伤筋动骨了。
综上所述,抽象类解决不了层出不穷的问题,远非什么灵丹妙药,只能用于处理符合条件的特定要求。若想真正有效应对这些刁钻古怪的挑战,还得指望新的抽象技术,在Java编程中这就是接口。接口不从属于类,而是与类平级,类通过关键字class标识,而接口通过关键字interface来标识。由于接口是作为类的辅助角色出现,因此它在结构上与类比较相似,不过也有不少不同之处,举例如下:
1、凡是类都有构造方法,即便是抽象类也支持定义构造方法,但接口不允许定义构造方法,因为接口只用于声明某些行为动作,本身并非一个实体。
2、在Java8以前,接口内部的所有方法都必须是抽象方法,具体的方法内部代码有赖于该接口的实现类来补充。因为有这个强制规定,所以接口内部方法的abstract前缀可加可不加,即使不加abstract,编译器也会默认把该方法当作抽象方法。
3、至于接口内部的属性,则默认为终态属性,即添加了final前缀的成员属性。当然这个final前缀也是可加可不加的,即使不加final,编译器仍会默认把该属性当作终态属性。
按照上述的接口规定,再来编写一个定义了动物行为的接口代码,其中主要包括飞翔方法、游泳方法、奔跑方法等,详细的接口定义代码示例如下:

//定义一个接口。接口主要声明一些特定的行为方法
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 接口名”,同时该类还要重写接口里的所有抽象方法。于是实现了行为接口的鹅类代码如下所示:

//定义一个实现了接口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的跳跃方法。下面是一个蛙类代码的简单例子:

//定义一个实现了接口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仍是一个普通的类,外部调用它并没有什么两样,具体的调用代码示例如下:

	// 演示某个类同时实现了多个接口
	private static void testMultiple() {
		Frog frog = new Frog();
		frog.swim(); // 实现了Behavior接口的swim方法
		frog.jump(); // 实现了Behavior2接口的run方法
	}

  

更多Java技术文章参见《Java开发笔记(序)章节目录

posted @ 2019-02-10 10:25  pinlantu  阅读(855)  评论(0编辑  收藏  举报