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

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

发现问题

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

寻找答案

于是我查了很多资料,发现:原来接口中的方法也是能有具体的实现的,这种改动源自于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修饰。

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

总的来说:

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

相关推荐
异常君4 分钟前
线程池隐患解析:为何阿里巴巴拒绝 Executors
java·后端·代码规范
Java技术小馆10 分钟前
SpringBoot中暗藏的设计模式
java·面试·架构
xiguolangzi11 分钟前
《springBoot3 中使用redis》
java
꧁坚持很酷꧂13 分钟前
配置Ubuntu18.04中的Qt Creator为中文(图文详解)
开发语言·qt·ubuntu
李菠菜18 分钟前
非SpringBoot环境下Jedis集群操作Redis实战指南
java·redis
不当菜虚困30 分钟前
JAVA设计模式——(四)门面模式
java·开发语言·设计模式
ruyingcai66666630 分钟前
用python进行OCR识别
开发语言·python·ocr
m0Java门徒38 分钟前
面向对象编程核心:封装、继承、多态与 static 关键字深度解析
java·运维·开发语言·intellij-idea·idea
liuweidong080241 分钟前
【Pandas】pandas DataFrame radd
开发语言·python·pandas
无心水1 小时前
【Java面试笔记:基础】8.对比Vector、ArrayList、LinkedList有何区别?
java·笔记·面试·vector·arraylist·linkedlist