谈谈Java的接口和作者的理解

写在前面

关于Java接口网络上有很多很好的资料,理论上完全没有必要写一篇文章来介绍Java接口,但是我想在这里谈一下我对Java接口的理解,如有错误或者与各位大佬意见不合的地方欢迎指出。

接口的简单介绍(详细介绍后面再说)

接口可以简单概括为,一系列抽象方法的集合。或者按照《Java核心技术》一书中所言“接口不是类,而是对希望符合这个接口的类的一组需求”。

接口中的方法没有实现,只有声明(默认方法我们稍后再说)。

以下可以声明一个接口

public interface InterfaceExample {
    void funExample();
}

接口不能创建实例,要使用它,我们需要在设计类的时候实现接口,同时实现接口中的抽象方法。

以下使用implements关键字定义一个类实现接口。

public class ClassExample implements InterfaceExample{

    @Override
    public void funExample() {
        System.out.println("实现了接口");
    }
}

为什么要有接口(接口有什么用)

1.向编译器保证类实现了某个方法

例如用Arrays.sort()方法对对象数组进行排序时就要求数组当中的元素一定要实现compareTo方法。而Java作为一个强类型语言,为确保安全,编译器在编译的时候就要确定数组当中的元素是否实现了compareTo方法,而使用接口就可以完美实现这一点。我们只需要定义一个接口,这个接口中只有compareTo这一个抽象方法,然后再要求调用Arrays.sort()的数组元素必须属于某个实现了这个接口的类就行了。编译器只需检查数组元素所属的类是不是实现了某个接口,而实现了某个接口的类当中一定有我们期望它拥有的方法。

Java的接口Comparable<>就是调用Arrays.sort()方法的数组元素所属的类所需要实现的,Comparable<>中只有一个compareTo抽象方法。

public class Student implements Comparable<Student>{
    private String name;
    private int id;

    public String getName() {
        return name;
    }

    public int getId() {
        return id;
    }

    public Student(){}
    public Student(String name,int id){
        this.name = name;
        this.id = id;
    }

    @Override
    public int compareTo(Student o) {
        return this.id-o.id;
    }
    @Override
    public String toString(){
        return name+" "+id;
    }
}
public class Main {
    public static void main(String[] args) {
        Random rand = new Random();
        Student[] students = new Student[10];
        for(int i=0;i<10;i++){
            students[i] = new Student("张"+(i+1),rand.nextInt(10000,20000));
        }
        Arrays.sort(students);
        for (Student student : students) {
            System.out.println(student);
        }
    }
}

2.满足多继承的需要

学习过c++的对多继承应该不会感到陌生

多继承指的是,一个子类可以继承自多个超类,Java是不允许多继承的。

原因也很简单,当出现如下一种情况时

也就是类B和类C都继承自类A,且类D同时继承自类B和类C时,会出现一个类D的对象当中有两个类A的的部分。对于允许多继承的编程语言来说(如c++),往往使用了复杂的方法来解决这个问题(c++使用了虚基类来避免这种情况,但这很复杂,所以c++程序员也很少用这个),Java为了避免变得跟c++一样复杂(毕竟Java在刚开始的时候以简单著称,相对与c++而言),所以取消了多继承。

但是多继承终归还是有需要的(不然c++为什么要提出这个概念呢),所以Java使用接口来实现多继承。类虽然只能继承只一个超类,但是可以实现多个接口。在实际的设计中,超类为主,用多个接口来提供辅助功能。

3.作为函数式接口传递代码块

接口的另一个重要作用就是作为函数式接口与lambda表达式一起使用,函数式接口就是只有一个抽象函数的接口。

因为Java没有加入函数类型,当我们需要传递一个代码块给一个函数时就需要通过接口。我们还是用Array.sort函数举例,但是这一回我不想用数组元素自带的compareTo方法进行排序或者元素本身就没有实现Comparable<>接口。

public class Student {
    private String name;
    private int id;

    public String getName() {
        return name;
    }

    public int getId() {
        return id;
    }

    public Student(){}
    public Student(String name,int id){
        this.name = name;
        this.id = id;
    }

    @Override
    public String toString(){
        return name+" "+id;
    }
}
public class Main {
    public static void main(String[] args) {
        Random rand = new Random();
        Student[] students = new Student[10];
        for(int i=0;i<10;i++){
            students[i] = new Student("张"+(i+1),rand.nextInt(10000,20000));
        }
        Arrays.sort(students,new comparator());
        for (Student student : students) {
            System.out.println(student);
        }
    }
}
class comparator implements Comparator<Student> {
    @Override
    public int compare(Student o1, Student o2) {
        return o1.getId()-o2.getId();
    }
}

这一回的Student类没有实现Comparable<>接口,而且我们向Arrays.sort函数当中传递了一个comparator类的对象,comparator类实现了另一个接口Comparator<>。

这是因为Arrays.sort方法有一个重载如下

public static <T> void sort(T[] a, Comparator<? super T> c)

可以看到sort的第二个参数是接口类型的,后面我们会说接口类型的变量可以引用实现了这个接口的类的对象。在这里我们实际上是通过接口传递了一个代码块进去,当Student类没有compareTo方法的时候,sort函数需要知道如何对Student进行排序,所以我们要传递一个代码块进去告诉sort如何排序。其他的支持函数式编程的语言如c++可以通过函数指针来传递代码块,但是Java并没有加入函数类型,只能通过对象的方式传递代码块(当然在实际开发当中使用lambda表达式更好,不过lambda表达式也是传递给函数式接口),接口在这里充当了重要作用,它告诉使用者该传入什么样的对象进去。

4.进行抽象定义,将定义和实现分开

Java集合类库当中的Collection,List,Map,Set等接口就是一个例子,如List接口的部分代码如下

public interface List<E> extends SequencedCollection<E> {
    boolean isEmpty();
    Iterator<E> iterator();
    boolean add(E e);
    boolean remove(Object o);
    boolean containsAll(Collection<?> c);
    boolean addAll(Collection<? extends E> c);
    boolean removeAll(Collection<?> c);
    boolean retainAll(Collection<?> c);
    void clear();
    boolean equals(Object o);
    int hashCode();
    E get(int index);
    E set(int index, E elemet);
    void add(int index, E element);
    E remove(int index);
    ListIterator<E> listIterator();
}

List接口只定义了List列表的行为,如可以加入元素,删除元素,判断是否为空,返回一个迭代器等等,并没有提供具体实现。那么,为什么要用接口将定义与实现分离呢,还是用List接口来举例说明,实现List列表有很多种方法,比如可以用链表来实现,也可以用动态的数组来实现。将接口与实现分离,List接口只管定义列表需要有什么功能,而具体使用什么方法实现就交给具体的类去做比如ArrayList类采取动态数组的方式,LinkedList采取链表的方式实现List接口

接口的字段和方法

接口中的字段

接口当中不可以包含实例字段,但是可以包含静态常量。

public interface InterfaceExample {
    public static final double PI = 3.1415926535;
}

不过上面声明常量的修饰符public static final是冗余的,接口中所有字段自动为public static final。

接口中的方法

默认方法

虽然接口是抽象方法的集合,但是你也可以为方法提供一个默认的实现,这样的方法叫做默认方法,用default修饰。

public interface InterfaceExample {
    default void fun(){
        System.out.println("fun");
    }
}

在使用默认方法的时候会出现一些意外,比如Student类的超类Base中定义了方法a,而Student类又实现了一个接口B,B也包含一个名字叫a的默认方法。这就会造成默认方法冲突。

Java解决默认方法冲突遵循两个原则:1.超类优先,2.向程序员报告接口冲突。我们一个个来解释

1.超类优先:如果接口与超类造成默认方法冲突,则只会考虑超类方法,接口的默认方法被忽略(不论有多少个接口造成冲突)

2.报告接口冲突:如果造成冲突的是两个接口的默认方法,那么编译器会把这个问题报告给程序员,程序员必须决定改使用哪个接口,具体实现方式是在Student类里面提供方法a的实现,在实现方法a的时候可以选择调用哪个接口的默认方法,也可以自己重新实现。

public class Main {
    public static void main(String[] args) {
        Student a = new Student();
        a.getInfo();
    }
}
class Student implements Person,Named{
    @Override
    public void getInfo() {
        Person.super.getInfo();//调用Person的方法
        Named.super.getInfo();//调用Named的方法
        System.out.println("Student");//自己实现方法
    }
}
interface Person{
    default void getInfo(){
        System.out.println("person");
    }
}
interface Named{
    default void getInfo(){
        System.out.println("name");
    }
}

需要注意的是,如果Peson和Named两个接口当中只有Person(或者Named)的getInfo是默认的,而另一个不是,那么编译器任然会报告冲突并要求程序员解决。 

静态方法

Java 8以后Java接口中的方法可以是静态的。这样做虽然有违接口的初衷,但是有时候确实需要静态方法。早期把静态方法放在与接口对应的伴随类里面。静态方法必须有实现(但它不是默认方法,不能使用default修饰)

public interface InterfaceExample {
    static void fun(){
        System.out.println("fun");
    }
}

方法的控制域

Java的接口中的方法起先都默认是public,而无需用public关键字修饰,Java 9开始接口中的方法可以是private的,但默认控制域仍然是public。且private方法必须提供实现(但它不是默认方法,不能使用default修饰)

private只能由接口内的其他方法调用,只作为辅助方法使用。

public interface InterfaceExample {
    void fun1();//默认是public
    public void fun2();//是public
    private void fun3() {//private,只能由其他方法调用
        System.out.println("fun3");
    }
}

总结

字段自动是public static final

实例方法通常是抽象的,但可以有默认实现,用default修饰。

可以包含静态方法且必须提供实现。

实例方法和静态方法自动是public,但是可以用private修饰,且私有方法必须要有实现,通常用作辅助方法

默认方法冲突遵循1.超类优先,2.向程序员报告接口冲突

接口变量

接口不是类,不能使用new对接口进行实例化

但可以声明接口变量,接口变量可以引用实现了接口的类的对象

public interface InterfaceExample {
    void funExample();
}
public class ClassExample implements InterfaceExample{
    @Override
    public void funExample() {
        System.out.println("实现了接口");
    }
}
public class Main {
    public static void main(String[] args) {
        InterfaceExample a = new ClassExample();
        a.funExample();
    }
}

最后

接口可以继承,接口A可以继承自接口B,当实现接口A的时候也要实现接口B的抽象方法。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值