先赞后看,养成习惯

文本已收录至GitHub开源仓库 Lu_JavaNodes 码云仓库地址Lu_JavaNodes,包含教程涉及所有思维导图,案例代码和后续讲解视频,欢迎Star增砖添瓦。

前言

在传统的接口语法中,接口中只可以有抽象方法。在是在实际的使用中,我们往往会需要用到很多和接口相关的功能(方法),这些功能会单独的拿出开放在工具类中。

工具类:类中所有的方法都是静态的

例如:Collection 和 Collocations,Collection 是一个集合接口,而我们需要很多集合相关的操作,像集合的排序,搜索等等, 这时候人们会把这些静态方法放在 Collections 工具类中。

在传统Java中我们经常会看到这样的情况,有一个接口叫 A,这时候就会有一个类叫 As,As中全是和A接口有关的静态方法。
例如:Executor 和 Executors

这样的一种方式总归来说是有点不方便。于是在JDK8中Java对于接口做了一些改动,允许将静态方法直接写入接口中。(接口中可以定义静态方法,静态方法肯定不是抽象的,是有实现的)。

接口的静态方法

代码案例

根据上述内容,我们来定义一个接口,在接口中写入一个静态方法。

public class TestStaticInterface {

    public static void main(String[] args) {
//        静态方法可以通过类名直接调用  接口可以说是特殊的类 所以通过接口名可以调用接口中的静态方法
        HelloInterface.printHello();
    } } interface HelloInterface{
    int hhh(); //    定义静态方法
    static void printHello(){
        System.out.println("Hello");
    }
}

运行代码可以看到如下结果

静态方法有什么用呢?

静态方法实际上是很实用的,最基本的用法:我们可以把产生接口对象的方法放在接口中。

什么意思???好,接下来我们通过代码演示一下。

假设现在我们有一个 Animal 接口,那么这时候如果要获得一个Animal类型的对象,我们要怎么做呢?

传统方法,创建一个Animals工具类,在其中有一个 static Animal createDog()可以获取一个Animal类型的对象,代码如下

public class TestStaticInterface {

    public static void main(String[] args) {
//        通过工具类获取对象
        Animal animal = Animals.createDog();
    }
} class Animals{
    //    静态方法获取对象
    static Animal createDog(){
//        局部内部类
        class Dog implements Animal{         }
//        返回对象
        return new Dog();
    }
}

但是当你拥抱JDK8的时候,一切都不一样了,因为有接口静态方法,可以直接将接口对象的获取放在接口的静态方法中。代码如下

public class TestStaticInterface {

    public static void main(String[] args) {
//        通过接口的静态方法获取一个Animal类型的对象
        Animal animal = Animal.createDog();
    }
} interface Animal{
//    静态方法获取对象
    static Animal createDog(){
//        局部内部类
        class Dog implements Animal{         }
//        返回对象
        return new Dog();
    }
}

在JDK 的 API 中是怎么使用静态方法的

接下来我们通过Java中的API来验证一下这种使用方法。通过API文档,可以找到 Comparator 接口(比较器),在这个接口中现在就有很多的静态方法(JDK8)。如图

通过这些静态方法,就可以通过接口直接获取比较器对象。

public class TestStaticInterface {

    public static void main(String[] args) {
//        通过Comparator接口获取一个自然排序的比较器(自然排序就是String中默认实现的排序逻辑)
        Comparator<String> comparator = Comparator.naturalOrder();
//        创建集合
        List<String> list = Arrays.asList("b","a","c");
//        通过比较器对集合进行排序
        list.sort(comparator);         for (String s : list) {
            System.out.println(s);
        }
    }
}

传统接口的另一个问题:向后兼容性不好

现在接口已经有了静态方法,但是传统的接口还有另一个问题。我们举例说明:

假设你正在公司中做项目,在你的代码中,有一个UserService的接口,接口中有一个方法String getUsernameById()

interface UserService{
    String getUsernameById();
}

该接口因为在项目中存在老长时间了,所以实现类众多,有100个实现类。

one day,领导希望你给这个接口中添加一个新的接口方法String getIdByUsername()。这样的需求意味着要修改100个实现类,不要说写代码了,删库跑路的心都有了。

这是一个极端的案例,但是说明了一个事儿,传统的接口向后兼容性不好,不易于维护和改造

而这个问题,在JDK8中得到了解决,解决方法就是:接口的默认方法

接口的默认方法

Java 8 中允许接口中包含具有具体实现的方法,该方法称为 “默认方法”,默认方法使用 default 关键字修饰

在接口中使用 default 表示这个方法有实现,接口中所有的方法都是 public

示例代码

interface UserService{
    String getUsernameById(); //    默认方法
    default void m1(){
        System.out.println("这是一个默认方法");
    }
} class UserServiceImpl implements UserService{     @Override
    public String getUsernameById() {
        return null;
    }
}

示例代码的问题

看了这样的一段代码,你一定会有一些疑问,我们一起来解决一下。

接口中的默认方法,实现类能不能继承到

答:这个当然是可以的,并且在实现类中依然可以进行方法的覆盖。

如果 UserServiceImpl 再有一个父类,父类中也有m1方法,那么UserServiceImpl 继承到的是父类还是接口中的m1方法

interface UserService{
    String getUsernameById(); //    默认方法
    default void m1(){
        System.out.println("这是一个默认方法");
    }
} //父类
class UserSer{
    public void m1(){
        System.out.println("这是一个默认方法");
    }
} class UserServiceImpl extends UserSer implements UserService{     @Override
    public String getUsernameById() {
        return null;
    }
}

答:在实现类中继承到的是父类中的。因为接口默认方法有”类优先”的原则

接口默认方法的”类优先”原则
若一个接口中定义了一个默认方法,而另外一个父类或接口中 又定义了一个同名的方法时

  • 选择父类中的方法。如果一个父类提供了具体的实现,那么
    接口中具有相同名称和参数的默认方法会被忽略。
  • 接口冲突。如果一个父接口提供一个默认方法,而另一个接 口也提供了一个具有相同名称和参数列表的方法(不管方法是否是默认方法),那么必须覆盖该方法来解决冲突

对于 JDK8 接口新语法的思考

关于接口新语法的讲解实际上已经结束了,但是想要和大家一起延伸一下思考,看下面一个案例。

interface IA{
    default void m2(){
        System.out.println("IA");
    }
} interface IB{
    default void m2(){
        System.out.println("IB");
    }
} class ImplC implements IA,IB{
//    接口冲突 通过覆盖解决
    public void m2(){
        System.out.println("Impl");
    }
}

以上代码实际上就是 “类优先”原则第二条接口冲突的演示代码,而我要思考的问题不是这个,而是:1.在实现类中,如何使用super,2.如果IA 和 IB 接口中的m2方法返回值不同怎么办?

1.在实现类中,如何使用super?

第一个问题,比较好解决,因为有m2来自两个接口,所以我们如果要调用super的话,需要说明要调用那个接口的super,语法:接口名.super.m2()

实现类继承的方法来自两个接口,必须覆盖,否则引用不明确。要调用super,也必须指明要调用那个接口。

其实这个问题来自多继承,过去接口比较简单,调用 super肯定不会调用接口,接口中方法都是抽象的,现在不一样了,父类和接口中都有方法实现,这时候再要调用就要指明要调用谁了。

虽然Java一直都是单继承,但是这个语法实际上已经是向多继承靠近了。只不过并没有把多继承正式的引入Java,所以会有一定的不足,这就是我们的第二个思考题。

2.如果IA 和 IB 接口中的m2方法返回值不同怎么办?

这其实也是一个标准的多继承的问题,在现版本没有解决。

在C++中其实就简单了,可以指定要覆盖谁

总结

学过了接口的静态方法和默认方法,仿佛发现了一个事儿,接口和抽象类越来越像了,那么这时候再问你那个问题:接口和抽象类有什么区别?

这个问题留给大家,好像以前背答案开始不好使了。

最后我们简单总结一下JDK8接口语法的新变化:在JDK8以后的接口中,允许有静态方法和默认方法(default)修饰

求关注,求点赞,求转发

欢迎关注本人公众号:鹿老师的Java笔记,将在长期更新Java技术图文教程和视频教程,Java学习经验,Java面试经验以及Java实战开发经验。

什么?接口中方法可以不是抽象的「JDK8接口新语法的深度思考」的更多相关文章

  1. 关于Override在JDK1.5和JDK1.6上子类实现接口中方法使用@Override注解编译错误.

    遇到这个问题说来也怪.新开了一个path的工作空间用来打patch.该Eclipse的默认全局的编译版本是1.6.但是唯独其中的一个插件项目的版本是1.5(可能是唯一的一个,不确定,不知道为什么会是这 ...

  2. java中抽象类与接口中方法访问修饰符问题 (

    1.抽象类中的抽象方法(其前有abstract修饰)不能用private.static.synchronized.native访问修饰符修饰.原 因如下:抽象方法没有方法体,是用来被继承的,所以不能用 ...

  3. Collection接口中方法的使用

    Collection:集合的接口 1.Collection和ArrayList和List的关系 ArrayList      implement(实现)       List List        ...

  4. java lambda怎么表达式判断被调用接口名称和接口中方法

    1.首先能够用于lambda表达式的只能是interface,并且interface 中只有一个方法. 这就说明,只要找到接口类型就能确定用的是哪个方法.(如下:intTypeInterface.St ...

  5. 接口中字段的修饰符:public static final(默认不写) 接口中方法的修饰符:public abstract(默认不写)abstract只能修饰类和方法 不能修饰字段

    abstract只能修饰类和方法 不能修饰字段

  6. Java接口中的方法

    接口中可以含有变量和方法.但是,接口中的变量会被隐式地指定为public static final变量(并且只能是public static final变量,用private修饰会报编译错误),而方法 ...

  7. 教你在Java接口中定义方法

    基本上所有的Java教程都会告诉我们Java接口的方法都是public.abstract类型的,没有方法体的. 但是在JDK8里面,你是可以突破这个界限的哦. 假设我们现在有一个接口:TimeClie ...

  8. java 接口中的成员变量与方法

    java接口中变量的默认修饰符为 public static final int i = 3; 相当于 public static final int i = 3; java接口中方法的默认修饰符为 ...

  9. 纳尼,java可以在接口中实现非抽象方法了?

    纳尼,接口中可以定义实例方法了?! 纳尼,接口中还可以定义静态方法了?! 没错,在Java8中新增了很多新的特性,其中就包括可以在接口中添加方法和变量. 首先我们来看下代码 public interf ...

随机推荐

  1. 讨论Java中的内部类是什么?

    目录 前言 what is that? 成员内部类 局部内部类 匿名内部类 why use it? how to use? 前言 内部类,讲完前面的特性,今天就讲下内部类这个用的比较多,出现频率挺高的 ...

  2. vnpy源码阅读学习(2):学习PyQt5

    PyQt5的学习 花费了一个下午把PyQt5大概的学习了下.找了一个教程 PyQt5教程 跟着挨着把上面的案例做了一遍,大概知道PyQt5是如何生成窗体,以及控件的.基本上做到如果有需求要实现,查查手 ...

  3. spark和mapreduce的区别

    spark和mapreduced 的区别map的时候处理的时候要落地磁盘 每一步都会落地磁盘 reduced端去拉去的话 基于磁盘的迭代spark是直接再内存中进行处理 dag 执行引擎是一个job的 ...

  4. [LOJ#3044][动态DP]「ZJOI2019」Minimax 搜索

    题目传送门 容易想到一种暴力 DP:先转化成对于每个 \(k\) 求出 \(\max_{i\in S}|i-w_i|\le k\) 的方案数,最后差分 然后问题转化成每个叶子的权值有个取值区间,注意这 ...

  5. leetcode 全解(python版)

    本着blabla的精神,在这里(可能会)给出leetcode的全部习题答案 --持续更新中... 1.给定一个整数数组 nums 和一个目标值 target,请你在该数组中找出和为目标值的那 两个 整 ...

  6. JPA_映射关联关系

    一:单项多对一的关联关系 例如:订单和客户 1.新创建订单类 package com.atguigu.jpa.helloworld; import javax.persistence.Column; ...

  7. JDK源码之Integer类分析

    一 简介 Integer是int基本类型的包装类,同样继承了Number类,实现了Comparable接口,String类中的一些转化方法就使用了Integer类中的一些API,且fianl修饰不可继 ...

  8. Spring实战:第一个spring mvc项目

    我自己看的pdf书中有几个小错误导致项目一直起不来,具体错误是: 此处的名称不一致导致的,此外对于映射@RequestMapping("/"),需要删除创建框架时自带的index. ...

  9. Kafka动态配置实现原理解析

    问题导读 Apache Kafka在全球各个领域各大公司获得广泛使用,得益于它强大的功能和不断完善的生态.其中Kafka动态配置是一个比较高频好用的功能,下面我们就来一探究竟. 动态配置是如何设计的? ...

  10. React报错Failed prop type: Invalid prop `component` of type `object` supplied to `Route`, expected `function`

    引言 最近在忙毕业设计,博客也很久没更新了,毕业设计使用vue做了一个校园寻物网站,现在开始学Raect,记录一下自己遇到问题,react-redux的connect方法使得组件与Redux建立了联系 ...