Java中的接口

一、接口的本质

数据类型的概念,是将对象看作属于某种数据类型,并按该类型进行操作,在一些情况下,并不能反映对象以及对对象操作的本质。

为什么这么说呢?很多时候,我们实际上关心的,并不是对象的类型,而是对象的能力,只要能提供这个能力,类型并不重要。我们来看一些生活中的例子。

比如要拍照,很多时候,只要能拍出符合需求的照片就行,至于是用手机拍,还是用Pad拍,或者是用单反相机拍,并不重要,即关心的是对象是否有拍出照片的能力,而并不关心对象到底是什么类型,手机、Pad或单反相机都可以。

又如要计算一组数字,只要能计算出正确结果即可,至于是由人心算,用算盘算,用计算器算,用计算机软件算,并不重要,即关心的是对象是否有计算的能力,而并不关心对象到底是算盘还是计算器。

再如要将冷水加热,只要能得到热水即可,至于是用电磁炉加热,用燃气灶加热,还是用电热水壶加热,并不重要,即重要的是对象是否有加热水的能力,而并不关心对象到底是什么类型。

在这些情况中,类型并不重要,重要的是能力。那如何表示能力呢?接口。下面就来详细介绍接口,包括其概念、用法、一些细节,以及如何用接口替代继承。

接口声明了一组能力,但它自己并没有实现这个能力,它只是一个约定。

接口涉及交互两方对象,一方需要实现这个接口,另一方使用这个接口,但双方对象并不直接互相依赖,它们只是通过接口间接交互。拿USB接口来说,USB协议约定了USB设备需要实现的能力,每个USB设备都需要实现这些能力,计算机使用USB协议与USB设备交互,计算机和USB设备互不依赖,但可以通过USB接口相互交互。下面我们来看Java中的接口。

二、定义接口

首先我们来定义一个接口,代码如下

public interface MyComparable{

        int comparTo(Object other);

}

定义接口的代码解释如下:

1)Java使用interface这个关键字来声明接口,修饰符一般都是public。

2)interface关键字后面就是接口的名字MyComparable。

3)接口定义里面,声明了一个方法comparTo,但是没有定义方法体,Java8之前,接口内不能实现方法。接口方法内不需要加修饰符,加与不加都相当于public abstract。

再来解释一下comparTo方法:

1)方法的参数是一个Object类型的变量other,表示另一个参与比较的对象。

2)第一个参与比较的对象是自己。

3)返回结果是int类型,-1表示自己小于参数对象,0表示相同,1表示大于参数对象。

接口与类不同,它的方法没有实现代码。定义一个接口并没有做什么,也没有太大的用处,它还需要至少两个参与者:一个需要实现接口,另一个使用接口。我们先来实现接口。

三、接口实现

实现的代码如下:

public class Point implements MyComparable {
    private int x;
    private int y;
    public Point(int x,int y){
        this.x = x;
        this.y = y;
    }
    public double distance(){
        return Math.sqrt(x*y+y*y);
    }

    @Override
    public String toString() {
        return "{"+x+", " +y+"}";
    }

    @Override
    public int comparTo(Object other) {
        if(!(other instanceof Point)){
            throw new IllegalArgumentException();
        }
        Point otherPoint = (Point)other;
        double delta = distance() - otherPoint.distance();
        if(delta <0){
            return -1;
        }else if (delta > 0 ){
            return 1;
        }else {
            return 0;
        }
    }
}

代码解释如下:

1)Java使用implements这个关键字表示实现接口,前面是类名,后面是接口名。

2)实现接口必须要实现接口中声明的方法,Point实现了compareTo方法。

再来解释Point的compareTo实现。

1)Point不能与其他类型的对象进行比较,它首先检查要比较的对象是否是Point类型,如果不是,使用throw抛出一个异常,异常将在下一章介绍,此处可以忽略。

2)如果是Point类型,则使用强制类型转换将Object类型的参数other转换为Point类型的参数otherPoint。

3)这种显式的类型检查和强制转换是可以使用泛型机制避免的。一个类可以实现多个接口,表明类的对象具备多种能力,各个接口之间以逗号分隔,语法如下所示:

public Class Test implements Interface1,Interface2{
    //主方法体
}

四、使用接口

与类不同,接口不能直接new出一个接口对象,对象只能通过类来创建。但可以声明接口类型的变量,引用实现了接口对象的类,如:

MyComparable P1 = new PoInt(2,3);
Mycomparable p2 = new PoInt(1,2);
system.out.print( p1.compareTo(p2))

p1和p2是MyComparable类型的变量,但引用了Point类型的对象,之所以能赋值是因为Point实现了MyComparable接口。如果一个类型实现了多个接口,那么这种类型的对象就可以被赋值给任一接口类型的变量。p1和p2可以调用MyComparable接口的方法,也只能调用MyComparable接口的方法,实际执行时,执行的是具体实现类的代码。

为什么Point类型的对象非要赋值给MyComparable类型的变量呢?在以上代码中,确实没必要。但在一些程序中,代码并不知道具体的类型,这才是接口发挥威力的地方。我们来看下面使用MyComparable接口的例子,代码如下所示:

public class CompUtil {
    public static Object max(MyComparable[] objs) {
        if (objs == null || objs.length == 0) {
            return null;
        }
        MyComparable max = objs[0];
        for (int i = 1; i < objs.length; i++) {
            if (max.compareTo(objs[i]) < 0) {
                max = objs[i];
            }
        }
        return max;
    }

    public static void sort(MyComparable[] objs) {
        for (int i = 0; i < objs.length; i++) {
            int min = i;
            for (int j = i + 1; j < objs.length; j++) {
                if (objs[j].compareTo(objs[min]) < 0) {
                    min = j;
                }
            }
            if (min != i) {
                MyComparable temp = objs[i];
                objs[i] = objs[min];
                objs[min] = temp;
            }
        }
    }
}

类CompUtil提供了两个方法,max获取传入数组中的最大值,sort对数组升序排序,参数都是MyComparable类型的数组,sort使用的是简单选择排序,具体算法我们就不介绍了。可以看出,这个类是针对MyComparable接口编程,它并不知道具体的类型是什么,也并不关心,但却可以对任意实现了MyComparable接口的类型进行操作。我们来看如何对Point类型进行操作,代码如下:

        Point[] points = new Point[]{
                new Point(2, 3), new Point(3, 4), new Point(1, 2)
        };
        System.out.println("max:" + CompUtil.max(points));
        CompUtil.sort(points);
        System.out.println("sort:" + Arrays.toString(points));
    

以上代码创建了一个Point类型的数组points,然后使用CompUtil的max方法获取最大值,使用sort排序,并输出结果,输出如下:watermark,type_d3F5LXplbmhlaQ,shadow_50,text_Q1NETiBA6K-t5LuE5peg6KiA,size_10,color_FFFFFF,t_70,g_se,x_16

 这里演示的是对Point数组操作,实际上可以针对任何实现了MyComparable接口的类型数组进行操作。这就是接口的威力,可以说,针对接口而非具体类型进行编程,是计算机程序的一种重要思维方式。

接口很多时候反映了对象以及对对象操作的本质。它的优点有很多,首先是代码复用,同一套代码可以处理多种不同类型的对象,只要这些对象都有相同的能力,如CompUtil。

接口更重要的是降低了耦合,提高了灵活性。使用接口的代码依赖的是接口本身,而非实现接口的具体类型,程序可以根据情况替换接口的实现,而不影响接口使用者。解决复杂问题的关键是分而治之,将复杂的大问题分解为小问题,但小问题之间不可能一点关系没有,分解的核心就是要降低耦合,提高灵活性,接口为恰当分解提供了有力的工具。

五、接口的一些细节

前面介绍了接口的基本内容,接口还有一些细节,包括:

1)接口中的变量。

2)接口的继承。

3)类的继承与接口。

4) instanceof。

下面具体介绍。

(1)接口中的变量接口中可以定义变量

语法如下所示:

    public interface Interface{
        public static final int a = 0;
    }

这里定义了一个变量int a,修饰符是public static final,但这个修饰符是可选的,即使不写,也是public static final。这个变量可以通过“接口名.变量名”的方式使用,如Interface1.a。

(2)接口的继承接口也可以继承

一个接口可以继承其他接口,继承的基本概念与类一样,但与类不同的是,接口可以有多个父接口,代码如下所示:

public interface IBase1{
    void method1();
}
public interface IBase2{
    void method2();
}
public interface IChild extends IBase1,IBase2{
}

IChild有IBase1和IBase2两个父类,接口的继承同样使用extends关键字,多个父接口之间以逗号分隔。

(3)类的继承与接口类的继承与接口可以共存

换句话说,类可以在继承基类的情况下,同时实现一个或多个接口,语法如下所示(用于演示,Base这个类并没有定义):

public class Child extends Base implements IChild(){
    //主体代码
}

关键字extends要放在implements之前。

(4)instanceof

与类一样,接口也可以使用instanceof关键字,用来判断一个对象是否实现了某接口,例如:

Point p = new Point(2,3);
if (p instanceof MyComparable){
     System.out.println("comparable");
}

六、使用接口替代继承

继承至少有两个好处:一个是复用代码;另一个是利用多态和动态绑定统一处理多种不同子类的对象。使用组合替代继承,可以复用代码,但不能统一处理。使用接口替代继承,针对接口编程,可以实现统一处理不同类型的对象,但接口没有代码实现,无法复用代码。将组合和接口结合起来替代继承,就既可以统一处理,又可以复用代码了。

七、 Java 8和Java 9对接口的增强

需要说明的是,前面介绍的都是Java 8之前的接口概念,Java 8和Java 9对接口做了一些增强。在Java 8之前,接口中的方法都是抽象方法,都没有实现体,Java 8允许在接口中定义两类新方法:静态方法和默认方法,它们有实现体,比如:

public interface IDemo {
    void hello();
    public static void test() {
        System.out.println("hello");
    }
    default void hi() {
        System.out.println("hi");
    }
}

test()就是一个静态方法,可以通过IDemo.test()调用。在接口不能定义静态方法之前,相关的静态方法往往定义在单独的类中,比如,Java API中,Collection接口有一个对应的单独的类Collections,在Java 8中,就可以直接写在接口中了,比如Comparator接口就定义了多个静态方法。

hi()是一个默认方法,用关键字default表示。默认方法与抽象方法都是接口的方法,不同在于,默认方法有默认的实现,实现类可以改变它的实现,也可以不改变。引入默认方法主要是函数式数据处理的需求,是为了便于给接口增加功能。

在没有默认方法之前,Java是很难给接口增加功能的,比如List接口,因为有太多非Java JDK控制的代码实现了该接口,如果给接口增加一个方法,则那些接口的实现就无法在新版Java上运行,必须改写代码,实现新的方法,这显然是无法接受的。函数式数据处理需要给一些接口增加一些新的方法,所以就有了默认方法的概念,接口增加了新方法,而接口现有的实现类也不需要必须实现。

在Java 8中,静态方法和默认方法都必须是public的,Java 9去除了这个限制,它们都可以是private的,引入private方法主要是为了方便多个静态或默认方法复用代码,比如:

public interface IDemoPrivate{
    private void common(){
        System.out.println("common");
    }
    default void actionA(){
        common();
    }
    default void actionB(){
        common();
    }
}

八、小结

我们谈了数据类型思维的局限,提到了很多时候关心的是能力,而非类型,所以引入了接口,介绍了Java中接口的概念和细节。针对接口编程是一种重要的程序思维方式,这种方式不仅可以复用代码,还可以降低耦合,提高灵活性,是分解复杂问题的一种重要工具。接口不能创建对象,没有任何实现代码(Java 8之前),而之前介绍的类都有完整的实现,都可以创建对象。Java中还有一个介于接口和类之间的概念:抽象类,它有什么用呢?如果有需要的话我会再整理一篇关于抽象类的文章,我目前也还是处于学习阶段,欢迎各位在评论区留言进行指导、交流。

 

  • 24
    点赞
  • 61
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值