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

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

发现问题

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

寻找答案

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

具体原因

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

默认方法的应用场景

我们先回忆一下接口的定义:接口里的方法默认都是public abstract修饰的

因此接口里的方法都是抽象方法,但是看完我的讲解,你会有不一样的发现

场景一:

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

总结来说就是:默认方法提供了一种在接口中添加新方法的方式,而不会破坏已有的实现类,这样做的好处就是:我们可以向接口中添加新的功能,但不会破坏已有的代码。

场景二:

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

默认方法的使用

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

java 复制代码
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 的关系,一个猫实现了 " 跑 " 接口,就表明它拥有了跑这个功能
    那是概念上来说。如果放在代码层面,其实都能为我们原来的类新增一些功能或属性以便我们直接调用。
java 复制代码
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修饰,用于提供与接口相关的实用方法或工具方法。

java 复制代码
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方法的内容。

java 复制代码
public interface Animal {
    //我们将这里改成static
      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();
    }
}

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

java 复制代码
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修饰。

说到最后,你应该对接口有了更加深入的了解了。

总的来说:

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

相关推荐
西猫雷婶8 分钟前
python学opencv|读取图像(二十一)使用cv2.circle()绘制圆形进阶
开发语言·python·opencv
kiiila8 分钟前
【Qt】对象树(生命周期管理)和字符集(cout打印乱码问题)
开发语言·qt
初晴~9 分钟前
【Redis分布式锁】高并发场景下秒杀业务的实现思路(集群模式)
java·数据库·redis·分布式·后端·spring·
小_太_阳34 分钟前
Scala_【2】变量和数据类型
开发语言·后端·scala·intellij-idea
直裾37 分钟前
scala借阅图书保存记录(三)
开发语言·后端·scala
黑胡子大叔的小屋1 小时前
基于springboot的海洋知识服务平台的设计与实现
java·spring boot·毕业设计
ThisIsClark1 小时前
【后端面试总结】深入解析进程和线程的区别
java·jvm·面试
唐 城1 小时前
curl 放弃对 Hyper Rust HTTP 后端的支持
开发语言·http·rust
雷神乐乐2 小时前
Spring学习(一)——Sping-XML
java·学习·spring
小林coding2 小时前
阿里云 Java 后端一面,什么难度?
java·后端·mysql·spring·阿里云