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开发笔记(序)章节目录》
Java开发笔记(五十九)Java8之后的扩展接口的更多相关文章
- Java开发笔记(十九)规律变化的for循环
前面介绍while循环时,有个名叫year的整型变量频繁出现,并且它是控制循环进出的关键要素.不管哪一种while写法,都存在三处与year有关的操作,分别是“year = 0”.“year<l ...
- Java开发笔记(九十九)定时器与定时任务
前面介绍了线程的几种运行方式,不管哪种方式,一旦调用了线程实例的start方法,都会立即启动线程的事务处理.然而某些业务场景在事务执行时间方面有特殊需求,例如期望延迟若干时间之后才开始事务运行,又如期 ...
- Java开发笔记(十五)短路逻辑运算的优势
前面提到逻辑运算只能操作布尔变量,这其实是不严谨的,因为经过Java编程实现,会发现“&”.“|”.“^”这几个逻辑符号竟然可以对数字进行运算.譬如下面的代码就直接对数字分别开展了“与”.“或 ...
- .Net开发笔记(十九) 创建一个可以可视化设计的对象
阅读本篇博客之前需要了解VS窗体设计器的工作原理,详细可参见本系列博客(十).(十一).(十二).必须需要知道的一条结论就是:处于窗体设计器(Form Designer)中的任何组件(包含控件,下同) ...
- Java学习笔记(十九)——Java 日志记录 AND log4j
[前面的话] 学习的进度应该稍微在快一点. Java日志到了必须学习怎么使用的时候了,因为在项目中要进行使用.基础性文章,选择性阅读. [结构] java日志对调试,记录运行,问题定位都起到了很重要的 ...
- JAVA学习第五十九课 — 网络编程概述
网络模型 OSI(Open System Interconnection)开放系统互连:參考模型 TCP/IP 网络通讯要素 IP地址 port号 传输协议 网络參考模型 七层OSI模型的基本概念要了 ...
- 【Java学习笔记之十九】super在Java继承中的用法小结
1)有人写了个很好的初始化属性的构造函数,而你仅仅想要在其中添加另一些自己新建属性的初始化,这样在一个构造函数中调用另外一个构造函数,可以避免重复的代码量,减少工作量: 2)在一个构造函数中调用另外一 ...
- Java开发笔记(十)一元运算符的技巧
前面讲到赋值运算符的时候,提到“x = x+7”可以被“x += 7”所取代,当然Java编程中给某个变量自加7并不常见,常见的是给某变量自加1,就像走台阶,一般都是一级一级台阶地走,犯不着一下子跳上 ...
- Java开发笔记(十二)布尔变量论道与或非
在编程语言的设计之初,它们除了可以进行数学计算,还常常用于逻辑推理和条件判断.为了实现逻辑判断的功能,Java引入了一种布尔类型boolean,用来表示“真”和“假”.该类型的变量只允许两个取值,即t ...
- Java开发笔记(十四)几种运算符的优先级顺序
到目前为止,我们已经学习了Java语言的好几种运算符,包括算术运算符.赋值运算符.逻辑运算符.关系运算符等基础运算符,并且在书写赋值语句时都没添加圆括号,显然是默认了先完成算术.逻辑.关系等运算,最后 ...
随机推荐
- 关于Django字段类型中 blank和null的区别
blank 设置为True时,字段可以为空.设置为False时,字段是必须填写的.字符型字段CharField和TextField是用空字符串来存储空值的. 如果为True,字段允许为空,默认不允许. ...
- ASP.NET Core知多少(7):对重复编译说NO -- dotnet watch
ASP.NET Core知多少系列:总体介绍及目录 1. 引言 我们一般的开发过程,就是编码-->编译-->运行-->调试-->定位问题--->修改代码-->编译- ...
- SQL Server 2017 新功能分享
本篇文章是我在MVP直通车分享的关于SQL Server 2017的新功能,现在ppt分享如下,可以点击这里下载.
- docker 及 docker-compose 的快速安装和简单使用
本篇将使用 DaoCloud 源在 Ubuntu 上简单快速安装 docker 及 docker-compose 并添加了通过 Dockerfile 及 docker-compose.yml 使用 n ...
- Javascript高级编程学习笔记(87)—— Canvas(4)绘制路径
绘制路径 2D上下文支持许多在画布上绘制路径的方法 通过路径可以创造出复杂的形状和线条,要绘制路径首先必须调用beginPath()方法,表示开始绘制路径 然后再通过下列的方法绘制路径: arc(x, ...
- js注入攻击
注入攻击一般指用户输入数据导致页面乃至整个网站.服务器异常的情况. 直接看一个例子: <html> <head> <title>Test</title> ...
- [Swift]LeetCode217. 存在重复元素 | Contains Duplicate
Given an array of integers, find if the array contains any duplicates. Your function should return t ...
- Python--(爬虫与数据库的连接)
(每一天都是属于你的!) Python对于初学后巩固基础的人还是更多的来接触python爬虫会更好一些,在Python爬虫中包含很多基础部分知识,并且在项目中会提升你的成功感!加油! 我在工作之余时间 ...
- 洛谷P1036选数(素数+组合数)
题目链接:https://www.luogu.org/problemnew/show/P1036 主要考两个知识点:判断一个数是否为素数.从n个数中选出m个数的组合 判断一个数是否为素数: 素数一定是 ...
- 分布式事务之如何基于RocketMQ的事务消息特性实现分布式系统的最终一致性?
导读 在之前的文章中我们介绍了如何基于RocketMQ搭建生产级消息集群,以及2PC.3PC和TCC等与分布式事务相关的基本概念(没有读过的读者详见