当前位置: 首页 > news >正文

接口中的方法到底能有具体实现吗?

探究一个问题:接口中的方法到底能有具体实现吗?

发现问题

这是我无意间发现的一个问题,有一天在写代码的时候突然发现,我实现了一个接口却没有将他的抽象方法重写完,这还不是问题,问题是他还不报错。这是怎么回事呢?按理说接口的定义不就是:一个类实现该接口就必须重写其所有方法吗,为啥我没有把他的抽象方法重写完,还不报错呢?难道是编译器出错了?

寻找答案

于是我查了很多资料,发现:原来接口中的方法也是能有具体的实现的,这种改动源自于java8(我们口中所说的java8,JDK8,JDK1.8其实都是同一个东西,这里我就这样叫了)。也就是说在java8之前是没有这种语法的。看来,要想在IT一行干的好,必须要与时俱进啊。

具体原因

自从 java8开始就允许了接口里的方法有具体的实现,这个方法包含:默认方法和静态方法。这两个种方法都有具体的应用场景

默认方法的应用场景

我们先回忆一下接口的定义:接口里的方法默认都是public abstract修饰的
因此接口里的方法都是抽象方法,但是看完我的讲解,你会有不一样的发现

场景一:

我们都知道一个软件在使用的过程中免不了要更新,这种更新有助于拓展新的功能、修复bug等。当然JDK也不例外,当用户在使用过程中遇到什么更加复杂的场景,那么JDK的开发团队就会想办法新增一些功能来应对这些场景。但是如果直接在我们原先定义好的接口上新增的话,那么实现这个接口的所有类必须要重写这个接口的所有方法。如果是这样,那些在公司中已经在运转的项目难道要停一段时间,先把方法给重写了吗?这显然是不现实的,如果要是这样搞,那谁还想继续升级JDK啊,升级一次就迎来一次经济损失。况且,即使重写了方法也是个空的实现,根本用不到(此时还用不到这新增的功能)。这就体现了默认方法的重要性。
总结来说就是:默认方法提供了一种在接口中添加新方法的方式,而不会破坏已有的实现类,这样做的好处就是:我们可以向接口中添加新的功能,但不会破坏已有的代码。

场景二:

每当我们实现一个接口的时候,总要重写这个接口的所有抽象方法,但是有一种可能:我们不需要将所有的方法重写完就能完成我们的业务。此时默认方法的作用也就体现出来了,新增默认方法没有重写的负担,也就是在后来实现这个接口的时候不用重写默认方法,但是可以直接调用,这也是接口里的方法具体化的好处,还能减少了代码的无用逻辑。

默认方法的使用

我们可以看到:Cat直接实现Animal但不重写bark方法时,如果接口里的方法是默认方法(带有default修饰),是不会报错的。

public interface Animal {//这个方法就是默认方法default void bark(){System.out.println("动物会叫");}
}public class Cat implements Animal{}

加上public不会报错,但是如果我们将abstract加上会发生报错,说明此时的方法已经不是abstract修饰了,而是public default修饰,说到这里不知道你会不会有点懵,为啥default和public一起用了,这两个不是同一级别的吗,不都是修饰限定符吗?其实这里是个特殊的用法。这里有一些百度的解释,可以了解一下。

在这里插入图片描述

在这里插入图片描述
因此我们就不用重写,直接能调用我们继承的属性了
其实说到这,继承(extend)和实现(implement)的区别也想一起说说,
说实话,他们有联系又有区别。但总结来说:

  • 继承是:is a 的关系,就比如猫是动物,猫继承了动物的属性
  • 实现是:has a 的关系,一个猫实现了 “ 跑 ” 接口,就表明它拥有了跑这个功能
    那是概念上来说。如果放在代码层面,其实都能为我们原来的类新增一些功能或属性以便我们直接调用。
public interface Animal {default void bark(){System.out.println("动物会叫");}
}public class Cat implements Animal{public static void main(String[] args) {//这里我们没有重写bark方法,但是能打印出来结果,说明默认方法被调用了Cat cat = new Cat();cat.bark();}
}

运行结果
在这里插入图片描述

静态方法的应用场景

想一想如果此时有一个类继承了两个接口,但是两个接口同时含有重名的方法时,此时如果你不进行重写,那么你实例化以后调用的到底是哪一个bark?可以看到IDEA都报错了,所以我们是不会知道到底会调用哪一个方法的,这就体现了静态方法的重要性。下面有图:
静态方法:默认使用public static修饰,用于提供与接口相关的实用方法或工具方法。

public interface Animal {default void bark(){System.out.println("动物会叫");}
}
public interface Creature {default void bark(){System.out.println("生物会叫");}
}
public class Cat implements Animal,Creature{public static void main(String[] args) {Cat cat =new Cat();cat.bark();}
}

在这里插入图片描述

静态方法的使用

此时我们将其中一个接口里的方法改成静态的,那么就不会发生报错,但是由于方法成了静态方法,就成了类的属性,而不是对象的属性了,所有我们使用new对象的方式是拿不到静态方法的,所以只能打印default方法的内容。

public interface Animal {//我们将这里改成staticstatic void bark(){System.out.println("动物会叫");}
}public interface Creature {default void bark(){System.out.println("生物会叫");}
}
public class Cat implements Animal,Creature{public static void main(String[] args) {Cat cat =new Cat();cat.bark();}
}

在这里插入图片描述

虽然不能使用对象调用,但是我们可以通过 类名. 方法名的方式来调用

public interface Animal {static void bark(){System.out.println("动物会叫");}
}public interface Creature {default void bark(){System.out.println("生物会叫");}
}
public class Cat implements Animal,Creature{public static void main(String[] args) {
//        Cat cat =new Cat();
//        cat.bark();Animal.bark();}
}

运行结果:
在这里插入图片描述
这也大大方便了我们。

思维拓展(我们身边的实例)

不知道你有没有注意过一些接口,能用于集合排序的Collections接口和 Map接口

在这里插入图片描述

Collections接口的源码:
可以看到这个sort方法就是static修饰的,因此能直接使用类名.方法名调用,这也就是我们能直接调用sort方法的原因。
你说我之前直接用这个方法的时候怎么没有想这么多呢?知道他能排序,便感觉又掌握了一个新的方法,感觉自己又进步了,殊不知真正的进步是追根溯源。
在这里插入图片描述
Map接口源码:
可以看到forEach方法被default方法修饰的,所以我们在实现完这个接口以后不用重写forEach方法,只需要new对象调用就行了。
在这里插入图片描述
承接上文,我们知道了接口里的方法并不一定是public abstract修饰了
也能是 public default ,public static修饰。

说到最后,你应该对接口有了更加深入的了解了。
总的来说:
其实接口就是相当于一个框架一样,他把一些事物的统一属性给抽象出来了,主要功能就是为了让我们在写代码的时候不跑偏,如果按照这个框架写,那么你就不会跑偏,这也是前人给我们总结的经验啊,唉,为了我们操碎了心。

相关文章:

  • 北京网站建设多少钱?
  • 辽宁网页制作哪家好_网站建设
  • 高端品牌网站建设_汉中网站制作
  • c# 排序、强转枚举
  • IS-IS协议
  • 某MDM主数据管理系统与微软Dynamic CRM系统(新加坡节点)集成案例
  • 大模型12:知识图谱 图数据库
  • 深入学习小程序第二天:事件处理与用户交互
  • Android 14 Power键亮灭屏流程
  • 深入理解小程序的渲染机制与性能优化策略
  • 镜像仓库 Registry 介绍及实践-http
  • python-learning47--高阶教程--基础阶段--python函数--高级用法-作用域
  • Java每日一练_模拟面试题6(JVM的GC过程)
  • 网络协议 从入门到精通系列讲解 - 总目录
  • 【rx rb rz】Centos/Linux rx、rb、rz命令详细介绍
  • React应用(基于react脚手架)
  • 攻防世界-web-ctf-upload
  • Ubuntu 安装 Snipaste
  • egg(89)--egg之redis的发布和订阅
  • JavaScript 是如何工作的:WebRTC 和对等网络的机制!
  • k个最大的数及变种小结
  • LeetCode541. Reverse String II -- 按步长反转字符串
  • OSS Web直传 (文件图片)
  • SegmentFault 2015 Top Rank
  • Spring-boot 启动时碰到的错误
  • vue-router的history模式发布配置
  • Work@Alibaba 阿里巴巴的企业应用构建之路
  • 精彩代码 vue.js
  • 前端面试之CSS3新特性
  • 原生JS动态加载JS、CSS文件及代码脚本
  • linux 淘宝开源监控工具tsar
  • Spring Batch JSON 支持
  • 我们雇佣了一只大猴子...
  • #常见电池型号介绍 常见电池尺寸是多少【详解】
  • #中国IT界的第一本漂流日记 传递IT正能量# 【分享得“IT漂友”勋章】
  • $.ajax()参数及用法
  • (52)只出现一次的数字III
  • (Java企业 / 公司项目)点赞业务系统设计-批量查询点赞状态(二)
  • (分布式缓存)Redis分片集群
  • (附源码)springboot电竞专题网站 毕业设计 641314
  • (含笔试题)深度解析数据在内存中的存储
  • (九)One-Wire总线-DS18B20
  • (利用IDEA+Maven)定制属于自己的jar包
  • (四)进入MySQL 【事务】
  • (算法二)滑动窗口
  • (一)搭建springboot+vue前后端分离项目--前端vue搭建
  • (总结)Linux下的暴力密码在线破解工具Hydra详解
  • .FileZilla的使用和主动模式被动模式介绍
  • .gitignore文件_Git:.gitignore
  • .NET / MSBuild 扩展编译时什么时候用 BeforeTargets / AfterTargets 什么时候用 DependsOnTargets?
  • .NET CORE Aws S3 使用
  • .NET Core工程编译事件$(TargetDir)变量为空引发的思考
  • .NET 材料检测系统崩溃分析
  • .Net 高效开发之不可错过的实用工具
  • .Net 基于MiniExcel的导入功能接口示例
  • .net 托管代码与非托管代码
  • .NetCore实践篇:分布式监控Zipkin持久化之殇
  • .NET国产化改造探索(一)、VMware安装银河麒麟