Java开发笔记(五十九)Java8之后的扩展接口
前面介绍了接口的基本用法,有心的朋友可能注意到这么一句话“在Java8以前,接口内部的所有方法都必须是抽象方法”,如此说来,在Java8之后,接口的内部方法也可能不是抽象方法了吗?之所以Java8对接口的定义规则发生变化,是因为原来的接口定义存在先天不足导致的,例如下列几点需求就难以满足:
1、Java8以前规定接口的内部方法只能是抽象方法,在该接口的实现类里面全部都要重写。这个规定明显太霸道了,为什么非得所有都重写呢?有的行为分明是通用的,比如呼吸动作,凡是陆上动物都用鼻子呼吸,把新鲜空气吸进去,再把循环后的空气呼出来,这个呼吸方法理应放之四海而皆准,根本无需在每个实现类中依次重写过去。
2、Java8以前的接口不支持构造方法也就算了,可是它居然也不支持静态成员(包括静态属性和静态方法)!这下可苦了程序员,因为与行为有关的常量与工具方法不能放到接口内部,只能另外写个工具类填入这些常量与工具方法,于是原本应当在一个屋檐之下的行为动作和行为概念不得不分居两地了。
有鉴于此,从Java8开始,接口顺应时代要求进行了规则修订,针对以上的两点需求分别补充了相应的处理对策:
1、增加了默认方法,并通过前缀default来标识。接口内部需要编写默认方法的完整实现代码,这样实现类无需重写该方法即可直接继承并使用,仿佛默认方法就是父类方法一样,唯一的区别在于实现类不允许重写默认方法。
2、增加了静态属性和静态方法,而且都通过前缀static来标识。接口的静态属性同时也是终态属性,初始化赋值之后便无法再次修改;接口的静态方法不能被实现类继承,因而实现类允许定义同名的静态方法,缘于接口的静态方法与实现类的静态方法没有任何关联,仅仅是它俩恰好同名而已。
据此对先前的行为接口Behavior进行增强,按照Java8的新特性补充了默认方法与静态方法,修补之后的新接口ExpandBehavior代码如下所示:
//定义一个增加了Java8新特性的接口 public interface ExpandBehavior { // 声明了一个抽象的飞翔方法 public void fly(); // 声明了一个抽象的游泳方法 public void swim(); // 声明了一个抽象的奔跑方法 public void run(); // 默认方法,以前缀default标识。默认方法不支持重写,但可以被继承。 public default String getOrigin(String place, String name, String ancestor) { return String.format("%s%s的祖先是%s。", place, name, ancestor); } public static int MALE = 0; public static int FEMALE = 1; // 接口内部的静态属性也默认为终态属性,所以final前缀可加可不加 //public final static int MALE = 0; //public final static int FEMALE = 1; // 静态方法,以关键字static标识。静态方法支持重写,但不能被继承。 public static String getNameByLeg(int leg_count) { if (leg_count == 2) { return "二足动物"; } else if (leg_count == 4) { return "四足动物"; } else if (leg_count >= 6) { return "多足动物"; } else { return "奇异动物"; } } }
根据上面的扩展接口,重新编写实现了该接口的鹅类,其中fly、swim、run这三个抽象方法均须重写,唯有默认方法getOrigin不要重写,并且鹅类代码当中可以直接调用这个默认方法。新写的鹅类代码ExpandGoose示例如下:
//定义实现了扩展接口的鹅类 public class ExpandGoose extends Bird implements ExpandBehavior { public ExpandGoose(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("槛外萧声轻荡漾,沙间鹅步满蹒跚。"); } // 根据产地和祖先拼接并打印该动物的描述文字 public void show(String place, String ancestor) { // getOrigin是来自扩展接口ExpandBehavior的默认方法,可以在实现类中直接使用 String desc = getOrigin(place, getName(), ancestor); System.out.println(desc); } }
接着轮到外部访问这个鹅类ExpandGoose了,表面上外部仍跟平常一样调用鹅类的成员方法,然而在调用接口的静态成员时有所差别。对于接口的静态属性,外部依然能够通过鹅类直接访问,访问格式形如“实现类的名称.静态属性名”;对于接口的静态方法,外部却不能通过鹅类访问了,因为实现类并未继承接口的静态方法,所以外部只能通过接口自身访问它的静态方法,访问格式形如“扩展接口的名称.静态方法名(***)”。下面是外部调用鹅类ExpandGoose的代码例子:
// 演示扩展接口的实现类用法 private static void testExpand() { // 实现类可以继承接口的静态属性 ExpandGoose goose = new ExpandGoose("鹅", ExpandGoose.FEMALE); goose.show("中国", "鸿雁"); goose.show("欧洲", "灰雁"); // 接口中的静态方法没有被实现类所继承,因而只能通过扩展接口自身访问 String typeName = ExpandBehavior.getNameByLeg(2); System.out.println("鹅是"+typeName); }
运行上面的测试代码,观察到如下的日志结果,可见不管是默认方法getOrigin,还是静态方法getNameByLeg,都得到了正确执行:
中国鹅的祖先是鸿雁。 欧洲鹅的祖先是灰雁。 鹅是二足动物
更多Java技术文章参见《Java开发笔记(序)章节目录》